claudeup 4.19.0 → 4.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/bin/claudeup.js +43 -19
  2. package/package.json +14 -19
  3. package/scripts/build-binaries.ts +77 -0
  4. package/scripts/check-optional-deps.ts +49 -0
  5. package/scripts/test-isolated.ts +43 -0
  6. package/src/__tests__/cli-router.test.ts +65 -0
  7. package/src/__tests__/conventions-integration.test.ts +745 -0
  8. package/src/__tests__/conventions-manager.test.ts +1172 -0
  9. package/src/__tests__/doctor-bins.test.ts +35 -0
  10. package/src/__tests__/doctor.test.ts +450 -0
  11. package/src/__tests__/dual-write-prevention.test.ts +14 -4
  12. package/src/__tests__/file-locking.test.ts +208 -0
  13. package/src/__tests__/gitignore-prerun.test.ts +1 -0
  14. package/src/__tests__/install-command.test.ts +81 -0
  15. package/src/__tests__/install-plan.test.ts +98 -0
  16. package/src/__tests__/manifest.test.ts +136 -0
  17. package/src/__tests__/marketplace-badge.test.ts +91 -0
  18. package/src/__tests__/marketplace-refresh.test.ts +267 -0
  19. package/src/__tests__/plugin-requires.test.ts +133 -0
  20. package/src/__tests__/plugin-setup.test.ts +7 -0
  21. package/src/__tests__/profile-command.test.ts +116 -0
  22. package/src/__tests__/profile-materializer.test.ts +82 -0
  23. package/src/__tests__/profile-sync.test.ts +136 -0
  24. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  25. package/src/__tests__/resolve-executable.test.ts +42 -0
  26. package/src/__tests__/resolver.test.ts +218 -0
  27. package/src/__tests__/symlink-manager.test.ts +99 -0
  28. package/src/__tests__/toolchain.test.ts +56 -0
  29. package/src/cli/claude.ts +21 -0
  30. package/src/cli/doctor.ts +132 -0
  31. package/src/cli/install.ts +360 -0
  32. package/src/cli/profile.ts +166 -0
  33. package/src/cli/router.ts +107 -0
  34. package/src/cli/update.ts +85 -0
  35. package/src/data/cli-tools.ts +7 -7
  36. package/src/data/gitignore-defaults.ts +4 -0
  37. package/src/data/marketplaces.ts +12 -0
  38. package/src/data/predefined-profiles.ts +3 -4
  39. package/src/main.tsx +11 -154
  40. package/src/prerunner/index.ts +62 -27
  41. package/src/services/claude-cli.ts +19 -22
  42. package/src/services/claude-settings.ts +99 -26
  43. package/src/services/conventions-manager.ts +865 -0
  44. package/src/services/doctor-bins.ts +49 -0
  45. package/src/services/doctor.ts +509 -0
  46. package/src/services/install-plan.ts +107 -0
  47. package/src/services/manifest.ts +166 -0
  48. package/src/services/marketplace-refresh.ts +173 -0
  49. package/src/services/plugin-manager.ts +7 -1
  50. package/src/services/plugin-requires.ts +215 -0
  51. package/src/services/plugin-version-check.ts +9 -4
  52. package/src/services/profile-materializer.ts +61 -0
  53. package/src/services/profile-sync.ts +150 -0
  54. package/src/services/resolver.ts +293 -0
  55. package/src/services/symlink-manager.ts +146 -0
  56. package/src/services/toolchain.ts +97 -0
  57. package/src/services/version-check.ts +10 -11
  58. package/src/types/index.ts +138 -31
  59. package/src/ui/App.tsx +0 -4
  60. package/src/ui/adapters/pluginsAdapter.ts +13 -0
  61. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  62. package/src/ui/screens/index.ts +0 -1
  63. package/src/ui/state/reducer.ts +0 -101
  64. package/src/ui/state/types.ts +0 -35
  65. package/src/utils/command-utils.ts +23 -0
  66. package/src/utils/file-locking.ts +144 -0
  67. package/src/data/alias-flags.js +0 -205
  68. package/src/data/cli-tools.js +0 -123
  69. package/src/data/gitignore-defaults.js +0 -24
  70. package/src/data/gitignore-reasons.js +0 -97
  71. package/src/data/gitignore-templates.js +0 -21
  72. package/src/data/marketplaces.js +0 -138
  73. package/src/data/mcp-servers.js +0 -509
  74. package/src/data/predefined-profiles.js +0 -248
  75. package/src/data/settings-catalog.js +0 -625
  76. package/src/data/skill-repos.js +0 -160
  77. package/src/data/statuslines.js +0 -159
  78. package/src/data/statuslines.ts +0 -188
  79. package/src/index.js +0 -4
  80. package/src/index.ts +0 -5
  81. package/src/main.js +0 -170
  82. package/src/prerunner/index.js +0 -252
  83. package/src/services/alias-settings.js +0 -51
  84. package/src/services/alias-shell-writer.js +0 -1018
  85. package/src/services/alias-store.js +0 -129
  86. package/src/services/claude-cli.js +0 -189
  87. package/src/services/claude-runner.js +0 -28
  88. package/src/services/claude-settings.js +0 -1223
  89. package/src/services/gitignore-detector.js +0 -155
  90. package/src/services/gitignore-fixer.js +0 -231
  91. package/src/services/gitignore-prerun.js +0 -46
  92. package/src/services/gitignore-resolver.js +0 -143
  93. package/src/services/gitignore-service.js +0 -117
  94. package/src/services/local-marketplace.js +0 -339
  95. package/src/services/marketplace-fetcher.js +0 -96
  96. package/src/services/marketplace-sync.js +0 -94
  97. package/src/services/mcp-registry.js +0 -87
  98. package/src/services/plugin-manager.js +0 -473
  99. package/src/services/plugin-mcp-config.js +0 -177
  100. package/src/services/plugin-setup.js +0 -499
  101. package/src/services/plugin-version-check.js +0 -262
  102. package/src/services/profiles.js +0 -161
  103. package/src/services/settings-manager.js +0 -243
  104. package/src/services/skills-manager.js +0 -393
  105. package/src/services/skillsmp-client.js +0 -87
  106. package/src/services/update-cache.js +0 -52
  107. package/src/services/version-check.js +0 -99
  108. package/src/types/gitignore.js +0 -6
  109. package/src/types/index.js +0 -1
  110. package/src/ui/App.js +0 -355
  111. package/src/ui/adapters/pluginsAdapter.js +0 -139
  112. package/src/ui/adapters/settingsAdapter.js +0 -111
  113. package/src/ui/adapters/skillsAdapter.js +0 -154
  114. package/src/ui/components/CategoryHeader.js +0 -9
  115. package/src/ui/components/EmptyFilterState.js +0 -4
  116. package/src/ui/components/FlagDetailEditor.js +0 -0
  117. package/src/ui/components/ScopeIndicator.js +0 -30
  118. package/src/ui/components/ScrollableList.js +0 -36
  119. package/src/ui/components/SearchInput.js +0 -19
  120. package/src/ui/components/StyledText.js +0 -39
  121. package/src/ui/components/TabBar.js +0 -19
  122. package/src/ui/components/layout/FooterHints.js +0 -29
  123. package/src/ui/components/layout/Panel.js +0 -6
  124. package/src/ui/components/layout/ProgressBar.js +0 -14
  125. package/src/ui/components/layout/ScopeTabs.js +0 -6
  126. package/src/ui/components/layout/ScreenLayout.js +0 -16
  127. package/src/ui/components/layout/index.js +0 -5
  128. package/src/ui/components/modals/ConfirmModal.js +0 -14
  129. package/src/ui/components/modals/InputModal.js +0 -5
  130. package/src/ui/components/modals/LoadingModal.js +0 -17
  131. package/src/ui/components/modals/MessageModal.js +0 -16
  132. package/src/ui/components/modals/ModalContainer.js +0 -137
  133. package/src/ui/components/modals/SelectModal.js +0 -18
  134. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  135. package/src/ui/components/modals/index.js +0 -6
  136. package/src/ui/components/primitives/ActionHints.js +0 -13
  137. package/src/ui/components/primitives/DetailSection.js +0 -7
  138. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  139. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  140. package/src/ui/components/primitives/MetaText.js +0 -8
  141. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  142. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  143. package/src/ui/components/primitives/SelectableRow.js +0 -5
  144. package/src/ui/components/primitives/index.js +0 -8
  145. package/src/ui/hooks/index.js +0 -4
  146. package/src/ui/hooks/useAsyncData.js +0 -77
  147. package/src/ui/hooks/useGitignoreModal.js +0 -74
  148. package/src/ui/hooks/useKeyboard.js +0 -13
  149. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  150. package/src/ui/hooks/useMismatchModal.js +0 -77
  151. package/src/ui/registry.js +0 -1
  152. package/src/ui/renderers/cliToolRenderers.js +0 -54
  153. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  154. package/src/ui/renderers/mcpRenderers.js +0 -26
  155. package/src/ui/renderers/pluginRenderers.js +0 -124
  156. package/src/ui/renderers/profileRenderers.js +0 -177
  157. package/src/ui/renderers/settingsRenderers.js +0 -73
  158. package/src/ui/renderers/skillRenderers.js +0 -138
  159. package/src/ui/screens/AliasScreen.js +0 -1111
  160. package/src/ui/screens/CliToolsScreen.js +0 -338
  161. package/src/ui/screens/EnvVarsScreen.js +0 -152
  162. package/src/ui/screens/GitignoreScreen.js +0 -328
  163. package/src/ui/screens/McpRegistryScreen.js +0 -238
  164. package/src/ui/screens/McpScreen.js +0 -176
  165. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  166. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  167. package/src/ui/screens/PluginsScreen.js +0 -769
  168. package/src/ui/screens/ProfilesScreen.js +0 -298
  169. package/src/ui/screens/SkillsScreen.js +0 -549
  170. package/src/ui/screens/StatusLineScreen.js +0 -206
  171. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  172. package/src/ui/screens/index.js +0 -10
  173. package/src/ui/state/AnimationContext.js +0 -34
  174. package/src/ui/state/AppContext.js +0 -162
  175. package/src/ui/state/DimensionsContext.js +0 -71
  176. package/src/ui/state/reducer.js +0 -547
  177. package/src/ui/state/types.js +0 -1
  178. package/src/ui/theme.js +0 -47
  179. package/src/utils/clipboard.js +0 -56
  180. package/src/utils/command-utils.js +0 -19
  181. package/src/utils/fuzzy-search.js +0 -101
  182. package/src/utils/string-utils.js +0 -62
@@ -1,129 +0,0 @@
1
- /**
2
- * In-memory types and defaults for the managed `claude` alias.
3
- *
4
- * Persistence has moved:
5
- * - Alias name lives in Claude Code's `settings.json` under `claudeup.aliasName`
6
- * (read/written via `alias-settings.ts`).
7
- * - Flag values are read from the user's shell rc file on screen mount
8
- * (via `parseAliasFromRc` in `alias-shell-writer.ts`). Edits stay in
9
- * memory until the user presses `w` to write the rc file.
10
- *
11
- * This module is filesystem-free so it can be imported by tests and pure
12
- * logic without dragging in `claude-settings.ts`'s module-level `os.homedir()`
13
- * constants.
14
- */
15
- import { ALIAS_FLAGS } from "../data/alias-flags.js";
16
- /** Default alias name. POSIX/fish-safe, short, and not in conflict with `claude`. */
17
- export const DEFAULT_ALIAS_NAME = "c";
18
- /**
19
- * Validate a candidate alias name. Returns null when valid, or a human
20
- * reason when invalid.
21
- *
22
- * We refuse anything that isn't `[A-Za-z_][A-Za-z0-9_-]*` — that's the
23
- * intersection of POSIX `name` rules and fish's `alias NAME …` accepting
24
- * set, and avoids the need to quote the name on either side of the `=`.
25
- */
26
- export function validateAliasName(name) {
27
- if (name.length === 0)
28
- return "Alias name cannot be empty.";
29
- if (name.length > 32)
30
- return "Alias name is unreasonably long (>32 chars).";
31
- if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(name)) {
32
- return "Alias name must start with a letter or underscore and contain only letters, digits, _ or -.";
33
- }
34
- return null;
35
- }
36
- /**
37
- * Build the default in-memory config. Used when no managed block exists in
38
- * the rc file (every flag disabled, default alias name).
39
- */
40
- export function defaultAliasConfig() {
41
- const flags = {};
42
- for (const flag of ALIAS_FLAGS) {
43
- flags[flag.id] = defaultValueFor(flag);
44
- }
45
- return { aliasName: DEFAULT_ALIAS_NAME, flags };
46
- }
47
- // ─── Channels ⊇ dev-load (derive-on-display) ───────────────────────────
48
- //
49
- // `--channels` is the approved-channel allowlist; `--dangerously-load-
50
- // development-channels` sideloads channels for local dev. A dev-loaded
51
- // channel must also run, so at the COMMAND LINE the channels list is the
52
- // union of both. We do NOT store that union — provenance would be lost
53
- // (you couldn't tell an own channel from a derived one). Instead:
54
- // - `channels.values` holds the user's OWN approved channels only.
55
- // - The dev-load flag is the single source of truth for derived channels.
56
- // - The editor shows them as a separate read-only section (display).
57
- // - The writer unions `own ∪ dev-load(if enabled)` at RENDER time.
58
- // This keeps the two lists distinct everywhere except the emitted alias.
59
- /** Id of the channels allowlist flag. */
60
- export const CHANNELS_FLAG_ID = "channels";
61
- /** Id of the dev-load flag whose values are derived into channels. */
62
- export const DEVLOAD_FLAG_ID = "dangerously-load-development-channels";
63
- /** Read the value list off a flag value, or `[]` for non-list kinds. */
64
- export function listValues(v) {
65
- return v && v.kind === "text-list" ? v.values : [];
66
- }
67
- /**
68
- * Channels that `--channels` derives from the dev-load flag for DISPLAY and
69
- * for the render-time union: the dev-load flag's values, but only when that
70
- * flag is enabled (a disabled dev-load contributes nothing to the alias).
71
- */
72
- export function derivedChannelValues(flags) {
73
- const devload = flags[DEVLOAD_FLAG_ID];
74
- if (!devload || devload.kind !== "text-list" || !devload.enabled)
75
- return [];
76
- return devload.values;
77
- }
78
- /**
79
- * Return a new flags map with `value` removed from the dev-load flag (the
80
- * SOURCE of derived channels). Used when the user deletes a "Dev-loaded
81
- * channels" row inside the --channels editor: the deletion must land on the
82
- * source flag, never on `channels.values`. Disables dev-load when emptied so
83
- * it stops emitting. Pure — leaves `channels.values` untouched.
84
- */
85
- export function withoutDerivedChannel(flags, value) {
86
- const cur = flags[DEVLOAD_FLAG_ID];
87
- if (!cur || cur.kind !== "text-list")
88
- return flags;
89
- const values = cur.values.filter((v) => v !== value);
90
- return {
91
- ...flags,
92
- [DEVLOAD_FLAG_ID]: {
93
- kind: "text-list",
94
- enabled: values.length > 0 && cur.enabled,
95
- values,
96
- },
97
- };
98
- }
99
- export function defaultValueFor(flag) {
100
- switch (flag.kind) {
101
- case "boolean":
102
- return { kind: "boolean", enabled: false };
103
- case "tri-state":
104
- return { kind: "tri-state", state: "unset" };
105
- case "select":
106
- return {
107
- kind: "select",
108
- enabled: false,
109
- value: flag.options?.[0]?.value ?? "",
110
- };
111
- case "text":
112
- return { kind: "text", enabled: false, value: "" };
113
- case "optional-text":
114
- return { kind: "optional-text", enabled: false, value: "" };
115
- case "text-list":
116
- return {
117
- kind: "text-list",
118
- enabled: false,
119
- values: [...(flag.defaultValues ?? [])],
120
- };
121
- case "multi-with-custom":
122
- return {
123
- kind: "multi-with-custom",
124
- enabled: false,
125
- picked: [],
126
- custom: [...(flag.defaultValues ?? [])],
127
- };
128
- }
129
- }
@@ -1,189 +0,0 @@
1
- /**
2
- * Claude CLI wrapper service
3
- *
4
- * Delegates plugin and marketplace management to the `claude` CLI
5
- * instead of manipulating settings JSON files directly.
6
- *
7
- * CLI commands used:
8
- * - claude plugin install/uninstall/enable/disable/update
9
- * - claude marketplace add
10
- */
11
- import { execFile } from "node:child_process";
12
- import { promisify } from "node:util";
13
- import { which } from "../utils/command-utils.js";
14
- import { isMarketplaceRegistered, removeGlobalInstalledPluginVersion, removeLocalInstalledPluginVersion, } from "./claude-settings.js";
15
- import { removeInstalledPluginVersion } from "./plugin-manager.js";
16
- import { defaultMarketplaces } from "../data/marketplaces.js";
17
- const execFileAsync = promisify(execFile);
18
- /**
19
- * Get the path to the claude CLI binary
20
- * @throws Error if claude CLI is not found in PATH
21
- */
22
- async function getClaudePath() {
23
- const claudePath = await which("claude");
24
- if (!claudePath) {
25
- throw new Error("claude CLI not found in PATH. Install with: npm install -g @anthropic-ai/claude-code");
26
- }
27
- return claudePath;
28
- }
29
- /**
30
- * Execute a claude CLI command and return stdout
31
- * @param args - Arguments to pass to claude
32
- * @param timeoutMs - Timeout in milliseconds (default: 30s)
33
- * @returns stdout from the command
34
- */
35
- async function execClaude(args, timeoutMs = 30000) {
36
- const claudePath = await getClaudePath();
37
- try {
38
- const { stdout } = await execFileAsync(claudePath, args, {
39
- timeout: timeoutMs,
40
- });
41
- return stdout.trim();
42
- }
43
- catch (error) {
44
- const execError = error;
45
- const msg = execError.stderr?.trim() ||
46
- execError.stdout?.trim() ||
47
- execError.message ||
48
- "claude command failed";
49
- throw new Error(msg);
50
- }
51
- }
52
- /**
53
- * Recover from a "not found in marketplace" error.
54
- *
55
- * Two distinct failure modes land in the same error string:
56
- * 1. Marketplace is registered locally but stale (or uses the old
57
- * "directory" source) — fix the registry and refresh the clone.
58
- * 2. Marketplace is not registered at all — look up its repo in
59
- * defaultMarketplaces and add it. This is the "new employee clones
60
- * a project whose .claude/settings.json enables @magus plugins but
61
- * magus was never registered on this machine" case.
62
- *
63
- * Returns true if recovery was attempted and a retry is worth trying.
64
- */
65
- async function recoverMissingMarketplace(marketplace) {
66
- if (await isMarketplaceRegistered(marketplace)) {
67
- // Registered but stale — existing recovery path.
68
- const { recoverMarketplaceSettings } = await import("./claude-settings.js");
69
- await recoverMarketplaceSettings();
70
- await updateMarketplace(marketplace);
71
- return true;
72
- }
73
- const defaultMp = defaultMarketplaces.find((m) => m.name === marketplace);
74
- if (!defaultMp?.source.repo)
75
- return false;
76
- await addMarketplace(defaultMp.source.repo);
77
- return true;
78
- }
79
- /**
80
- * Install a plugin using claude CLI
81
- * Handles enabling + version tracking + cache copy in one shot.
82
- *
83
- * If the install fails because the plugin is "not found in marketplace",
84
- * attempts to recover (add the marketplace if missing, or refresh it if
85
- * stale) and retries once.
86
- */
87
- export async function installPlugin(pluginId, scope = "user") {
88
- try {
89
- await execClaude(["plugin", "install", pluginId, "--scope", scope]);
90
- }
91
- catch (error) {
92
- const msg = error instanceof Error ? error.message : String(error);
93
- if (msg.includes("not found in marketplace")) {
94
- const marketplace = pluginId.split("@")[1];
95
- if (marketplace && (await recoverMissingMarketplace(marketplace))) {
96
- await execClaude([
97
- "plugin",
98
- "install",
99
- pluginId,
100
- "--scope",
101
- scope,
102
- ]);
103
- return;
104
- }
105
- }
106
- throw error;
107
- }
108
- }
109
- /**
110
- * Uninstall a plugin using claude CLI.
111
- * Falls back to direct settings removal if CLI uninstall fails
112
- * (e.g., for plugins installed via old JSON method).
113
- * @param projectPath - Required for project/local scope fallback
114
- */
115
- export async function uninstallPlugin(pluginId, scope = "user", projectPath) {
116
- try {
117
- await execClaude(["plugin", "uninstall", pluginId, "--scope", scope]);
118
- }
119
- catch {
120
- // Fallback: directly remove from settings JSON
121
- if (scope === "user") {
122
- await removeGlobalInstalledPluginVersion(pluginId);
123
- }
124
- else if (scope === "local" && projectPath) {
125
- await removeLocalInstalledPluginVersion(pluginId, projectPath);
126
- }
127
- else if (projectPath) {
128
- await removeInstalledPluginVersion(pluginId, projectPath);
129
- }
130
- }
131
- }
132
- /**
133
- * Enable a previously disabled plugin
134
- */
135
- export async function enablePlugin(pluginId, scope = "user") {
136
- await execClaude(["plugin", "enable", pluginId, "--scope", scope]);
137
- }
138
- /**
139
- * Disable a plugin without uninstalling it
140
- */
141
- export async function disablePlugin(pluginId, scope = "user") {
142
- await execClaude(["plugin", "disable", pluginId, "--scope", scope]);
143
- }
144
- /**
145
- * Update a plugin to the latest version.
146
- * Uses `install` as primary method since `update` only works for plugins
147
- * originally installed via the CLI. `install` handles both fresh installs
148
- * and re-installs (upgrades) of existing plugins regardless of how they
149
- * were originally added.
150
- *
151
- * Retries with marketplace update on "not found" errors (same as installPlugin).
152
- */
153
- export async function updatePlugin(pluginId, scope = "user") {
154
- try {
155
- await execClaude(["plugin", "install", pluginId, "--scope", scope], 60000);
156
- }
157
- catch (error) {
158
- const msg = error instanceof Error ? error.message : String(error);
159
- if (msg.includes("not found in marketplace")) {
160
- const marketplace = pluginId.split("@")[1];
161
- if (marketplace && (await recoverMissingMarketplace(marketplace))) {
162
- await execClaude(["plugin", "install", pluginId, "--scope", scope], 60000);
163
- return;
164
- }
165
- }
166
- throw error;
167
- }
168
- }
169
- /**
170
- * Add a marketplace by GitHub repo (e.g., "MadAppGang/magus")
171
- * Uses longer timeout since this involves cloning a git repo
172
- */
173
- export async function addMarketplace(repo) {
174
- await execClaude(["plugin", "marketplace", "add", repo], 60000);
175
- }
176
- /**
177
- * Update marketplace cache by running git pull via Claude CLI
178
- * Uses longer timeout since this involves git network operations
179
- */
180
- export async function updateMarketplace(name) {
181
- await execClaude(["plugin", "marketplace", "update", name], 60000);
182
- }
183
- /**
184
- * Check if the claude CLI is available
185
- * @returns true if claude CLI is found in PATH
186
- */
187
- export async function isClaudeAvailable() {
188
- return (await which("claude")) !== null;
189
- }
@@ -1,28 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { which } from "../utils/command-utils.js";
3
- /**
4
- * Execute claude CLI with provided arguments
5
- * @param args - Arguments to pass to claude
6
- * @returns Exit code from claude process
7
- */
8
- export async function runClaude(args) {
9
- // Check if claude exists
10
- const claudePath = await which("claude");
11
- if (!claudePath) {
12
- console.error("Error: claude CLI not found in PATH");
13
- console.error("Install with: npm install -g @anthropic-ai/claude-code");
14
- return 1;
15
- }
16
- // Spawn claude with full TTY inheritance using the resolved path
17
- return new Promise((resolve) => {
18
- const proc = spawn(claudePath, args, {
19
- stdio: "inherit", // Pass through stdin, stdout, stderr
20
- shell: false, // Use full path, no shell needed (fixes DEP0190)
21
- });
22
- proc.on("exit", (code) => resolve(code || 0));
23
- proc.on("error", (err) => {
24
- console.error("Failed to run claude:", err.message);
25
- resolve(1);
26
- });
27
- });
28
- }