@bastani/atomic 0.5.18 → 0.5.19

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 (53) hide show
  1. package/.agents/skills/workflow-creator/SKILL.md +110 -1
  2. package/.agents/skills/workflow-creator/references/workflow-inputs.md +10 -0
  3. package/.mcp.json +9 -0
  4. package/.opencode/opencode.json +5 -2
  5. package/README.md +394 -645
  6. package/assets/settings.schema.json +0 -20
  7. package/dist/sdk/components/attached-statusline.d.ts +13 -0
  8. package/dist/sdk/components/attached-statusline.d.ts.map +1 -0
  9. package/dist/sdk/components/header.d.ts.map +1 -1
  10. package/dist/sdk/components/session-graph-panel.d.ts.map +1 -1
  11. package/dist/sdk/components/statusline.d.ts +1 -3
  12. package/dist/sdk/components/statusline.d.ts.map +1 -1
  13. package/dist/sdk/providers/claude.d.ts +16 -5
  14. package/dist/sdk/providers/claude.d.ts.map +1 -1
  15. package/dist/sdk/runtime/executor.d.ts +63 -0
  16. package/dist/sdk/runtime/executor.d.ts.map +1 -1
  17. package/dist/sdk/runtime/tmux.d.ts +0 -9
  18. package/dist/sdk/runtime/tmux.d.ts.map +1 -1
  19. package/dist/services/config/atomic-config.d.ts +1 -7
  20. package/dist/services/config/atomic-config.d.ts.map +1 -1
  21. package/dist/services/config/definitions.d.ts +0 -45
  22. package/dist/services/config/definitions.d.ts.map +1 -1
  23. package/dist/services/config/index.d.ts +1 -1
  24. package/dist/theme/colors.d.ts +33 -0
  25. package/dist/theme/colors.d.ts.map +1 -0
  26. package/package.json +3 -2
  27. package/src/cli.ts +16 -1
  28. package/src/commands/cli/chat/index.ts +1 -1
  29. package/src/commands/cli/footer.tsx +118 -0
  30. package/src/commands/cli/init/index.ts +6 -89
  31. package/src/commands/cli/workflow-command.test.ts +146 -0
  32. package/src/commands/cli/workflow.ts +43 -7
  33. package/src/completions/bash.ts +3 -8
  34. package/src/completions/fish.ts +1 -3
  35. package/src/completions/powershell.ts +1 -17
  36. package/src/completions/zsh.ts +0 -2
  37. package/src/scripts/bundle-configs.ts +0 -12
  38. package/src/sdk/components/attached-statusline.tsx +33 -0
  39. package/src/sdk/components/header.tsx +16 -2
  40. package/src/sdk/components/session-graph-panel.tsx +10 -51
  41. package/src/sdk/components/statusline.tsx +0 -17
  42. package/src/sdk/providers/claude.ts +179 -177
  43. package/src/sdk/runtime/executor-entry.ts +3 -1
  44. package/src/sdk/runtime/executor.test.ts +292 -1
  45. package/src/sdk/runtime/executor.ts +222 -1
  46. package/src/sdk/runtime/tmux.conf +35 -4
  47. package/src/sdk/runtime/tmux.ts +0 -22
  48. package/src/services/config/atomic-config.ts +1 -14
  49. package/src/services/config/definitions.ts +1 -102
  50. package/src/services/config/index.ts +1 -1
  51. package/src/services/config/settings.ts +2 -65
  52. package/src/services/system/skills.ts +2 -19
  53. package/src/commands/cli/init/scm.ts +0 -175
@@ -30,28 +30,6 @@ export type TmuxResult =
30
30
  // Core tmux primitives
31
31
  // ---------------------------------------------------------------------------
32
32
 
33
- // ---------------------------------------------------------------------------
34
- // Default status-bar values — must match tmux.conf.
35
- // Centralised here so restore logic in session-graph-panel stays in sync.
36
- // ---------------------------------------------------------------------------
37
-
38
- export const TMUX_DEFAULT_STATUS_LEFT = " ";
39
- export const TMUX_DEFAULT_STATUS_LEFT_LENGTH = "10";
40
- export const TMUX_DEFAULT_STATUS_RIGHT = " #{session_name} | %H:%M ";
41
- export const TMUX_DEFAULT_STATUS_RIGHT_LENGTH = "60";
42
-
43
- // Attached-mode status bar — agent list via tmux window list + shortcut hints.
44
- // The window-status formats hide window 0 (orchestrator) and style agent names.
45
- // tmux natively highlights the current window, so no React state sync is needed
46
- // for agent cycling via Ctrl+\.
47
- export const TMUX_ATTACHED_STATUS_RIGHT =
48
- "#[fg=#cdd6f4]ctrl+g #[fg=#6c7086]graph #[fg=#585b70]\u00b7 #[fg=#cdd6f4]ctrl+\\ #[fg=#6c7086]next ";
49
- export const TMUX_ATTACHED_STATUS_RIGHT_LENGTH = "40";
50
- export const TMUX_ATTACHED_WINDOW_FMT =
51
- "#{?#{==:#{window_index},0},, #W }";
52
- export const TMUX_ATTACHED_WINDOW_STYLE = "fg=#6c7086";
53
- export const TMUX_ATTACHED_WINDOW_CURRENT_STYLE = "fg=#cdd6f4,bold";
54
-
55
33
  // ---------------------------------------------------------------------------
56
34
  // Core tmux primitives
57
35
  // ---------------------------------------------------------------------------
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { join, dirname } from "node:path";
11
11
  import { homedir } from "node:os";
12
- import { type SourceControlType, type AgentKey, type ProviderOverrides } from "./index.ts";
12
+ import { type AgentKey, type ProviderOverrides } from "./index.ts";
13
13
  import { SETTINGS_SCHEMA_URL } from "./settings-schema.ts";
14
14
  import { ensureDir } from "../system/copy.ts";
15
15
 
@@ -22,8 +22,6 @@ const SETTINGS_FILENAME = "settings.json";
22
22
  export interface AtomicConfig {
23
23
  /** Version of config schema */
24
24
  version?: number;
25
- /** Selected source control type */
26
- scm?: SourceControlType;
27
25
  /** Timestamp of last init */
28
26
  lastUpdated?: string;
29
27
  /** Per-provider overrides for chatFlags and envVars */
@@ -89,11 +87,9 @@ function pickAtomicConfig(record: JsonRecord | null): AtomicConfig | null {
89
87
 
90
88
  const config: AtomicConfig = {};
91
89
  const version = record.version;
92
- const scm = record.scm;
93
90
  const lastUpdated = record.lastUpdated;
94
91
 
95
92
  if (typeof version === "number") config.version = version;
96
- if (typeof scm === "string") config.scm = scm as SourceControlType;
97
93
  if (typeof lastUpdated === "string") config.lastUpdated = lastUpdated;
98
94
 
99
95
  const providers = pickProviders(record.providers);
@@ -137,7 +133,6 @@ function mergeConfigs(...configs: Array<AtomicConfig | null>): AtomicConfig | nu
137
133
  for (const config of configs) {
138
134
  if (!config) continue;
139
135
  if (config.version !== undefined) merged.version = config.version;
140
- if (config.scm !== undefined) merged.scm = config.scm;
141
136
  if (config.lastUpdated !== undefined) merged.lastUpdated = config.lastUpdated;
142
137
 
143
138
  if (config.providers) {
@@ -192,14 +187,6 @@ export async function saveAtomicConfig(
192
187
  await Bun.write(localPath, JSON.stringify(nextSettings, null, 2) + "\n");
193
188
  }
194
189
 
195
- /**
196
- * Get selected SCM using local override + global fallback.
197
- */
198
- export async function getSelectedScm(projectDir: string): Promise<SourceControlType | null> {
199
- const config = await readAtomicConfig(projectDir);
200
- return config?.scm ?? null;
201
- }
202
-
203
190
  /**
204
191
  * Resolve provider overrides from global + local settings (local wins).
205
192
  *
@@ -2,9 +2,6 @@
2
2
  * Agent configuration definitions for atomic CLI
3
3
  */
4
4
 
5
- import { stat } from "node:fs/promises";
6
- import { join } from "node:path";
7
-
8
5
  export interface AgentConfig {
9
6
  /** Display name for the agent */
10
7
  name: string;
@@ -88,13 +85,7 @@ export const AGENT_CONFIG: Record<AgentKey, AgentConfig> = {
88
85
  install_url:
89
86
  "https://github.com/github/copilot-cli?tab=readme-ov-file#installation",
90
87
  exclude: ["workflows", "dependabot.yml"],
91
- onboarding_files: [
92
- {
93
- source: ".mcp.json",
94
- destination: ".mcp.json",
95
- merge: true,
96
- },
97
- ],
88
+ onboarding_files: [],
98
89
  },
99
90
  };
100
91
 
@@ -122,95 +113,3 @@ export function getAgentConfig(key: AgentKey): AgentConfig {
122
113
  export function getAgentKeys(): AgentKey[] {
123
114
  return [...AGENT_KEYS];
124
115
  }
125
-
126
- /**
127
- * Source Control Management (SCM) configuration definitions
128
- */
129
-
130
- /** SCM keys for iteration */
131
- const SCM_KEYS = ["github", "sapling"] as const;
132
-
133
- /** Supported source control types — derived from SCM_KEYS tuple. */
134
- export type SourceControlType = (typeof SCM_KEYS)[number];
135
-
136
- export interface ScmConfig {
137
- /** Display name for prompts */
138
- displayName: string;
139
- /** Primary CLI tool (git or sl) */
140
- cliTool: string;
141
- /** Code review system (github, phabricator) */
142
- reviewSystem: string;
143
- /** Directory marker used to detect this SCM in a repo (e.g. `.git`, `.sl`) */
144
- detectDir: string;
145
- }
146
-
147
- export const SCM_CONFIG: Record<SourceControlType, ScmConfig> = {
148
- github: {
149
- displayName: "GitHub / Git",
150
- cliTool: "git",
151
- reviewSystem: "github",
152
- detectDir: ".git",
153
- },
154
- sapling: {
155
- displayName: "Sapling + Phabricator",
156
- cliTool: "sl",
157
- reviewSystem: "phabricator",
158
- detectDir: ".sl",
159
- },
160
- };
161
-
162
- /**
163
- * SCM-variant skill names, grouped by source control type.
164
- *
165
- * These are the skills that `installGlobalSkills` removes from the global
166
- * scope after the initial install, and that `installLocalScmSkills`
167
- * re-installs per-project based on the user's selected SCM. Passed to
168
- * `npx skills add --skill <name>` as explicit names (the skills CLI does
169
- * not support glob patterns like `gh-*`).
170
- */
171
- export const SCM_SKILLS_BY_TYPE: Record<SourceControlType, readonly string[]> =
172
- {
173
- github: ["gh-commit", "gh-create-pr"],
174
- sapling: ["sl-commit", "sl-submit-diff"],
175
- };
176
-
177
- /** Flat list of every SCM-variant skill across all source control types. */
178
- export const ALL_SCM_SKILLS: readonly string[] = [
179
- ...SCM_SKILLS_BY_TYPE.github,
180
- ...SCM_SKILLS_BY_TYPE.sapling,
181
- ];
182
-
183
- /**
184
- * Get all SCM keys for iteration
185
- */
186
- export function getScmKeys(): SourceControlType[] {
187
- return [...SCM_KEYS];
188
- }
189
-
190
- /**
191
- * Check if a string is a valid SCM type
192
- */
193
- export function isValidScm(key: string): key is SourceControlType {
194
- return key in SCM_CONFIG;
195
- }
196
-
197
- /**
198
- * Detect the SCM type by looking for marker directories in `projectRoot`.
199
- *
200
- * Checks each {@link ScmConfig.detectDir} (e.g. `.git`, `.sl`) and returns
201
- * the first match. Returns `null` when no known marker is found.
202
- */
203
- export async function detectScmType(
204
- projectRoot: string,
205
- ): Promise<SourceControlType | null> {
206
- for (const key of getScmKeys()) {
207
- const markerPath = join(projectRoot, SCM_CONFIG[key].detectDir);
208
- try {
209
- await stat(markerPath);
210
- return key;
211
- } catch {
212
- // marker not found — try next
213
- }
214
- }
215
- return null;
216
- }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Configuration Module Exports
3
3
  *
4
- * Centralized access to the CLI's agent and SCM configuration.
4
+ * Centralized access to the CLI's agent configuration.
5
5
  */
6
6
 
7
7
  export * from "./definitions.ts";
@@ -9,24 +9,17 @@
9
9
  * The --model CLI flag takes precedence over both (handled at call site).
10
10
  */
11
11
 
12
- import { join, dirname, resolve } from "node:path";
12
+ import { join, dirname } from "node:path";
13
13
  import { homedir } from "node:os";
14
14
  import { SETTINGS_SCHEMA_URL } from "./settings-schema.ts";
15
15
  import { ensureDir } from "../system/copy.ts";
16
16
  import { errorMessage } from "../../sdk/errors.ts";
17
- import type { AgentKey, ProviderOverrides, SourceControlType } from "./definitions.ts";
18
-
19
- export interface TrustedPathEntry {
20
- workspacePath: string;
21
- provider: AgentKey;
22
- }
17
+ import type { AgentKey, ProviderOverrides } from "./definitions.ts";
23
18
 
24
19
  interface AtomicSettings {
25
20
  $schema?: string;
26
- scm?: SourceControlType;
27
21
  version?: number;
28
22
  lastUpdated?: string;
29
- trustedPaths?: TrustedPathEntry[];
30
23
  telemetryEnabled?: boolean;
31
24
  providers?: Partial<Record<AgentKey, ProviderOverrides>>;
32
25
  }
@@ -59,62 +52,6 @@ async function writeGlobalSettings(settings: AtomicSettings): Promise<void> {
59
52
  await Bun.write(path, JSON.stringify(settings, null, 2));
60
53
  }
61
54
 
62
- function normalizeTrustedPathEntry(entry: TrustedPathEntry): TrustedPathEntry {
63
- return {
64
- workspacePath: resolve(entry.workspacePath),
65
- provider: entry.provider,
66
- };
67
- }
68
-
69
- function normalizeTrustedPaths(entries: TrustedPathEntry[] | undefined): TrustedPathEntry[] {
70
- const deduped = new Map<string, TrustedPathEntry>();
71
-
72
- for (const entry of entries ?? []) {
73
- if (
74
- typeof entry.workspacePath !== "string" ||
75
- typeof entry.provider !== "string"
76
- ) {
77
- continue;
78
- }
79
-
80
- const normalizedEntry = normalizeTrustedPathEntry(entry);
81
- deduped.set(
82
- `${normalizedEntry.provider}:${normalizedEntry.workspacePath}`,
83
- normalizedEntry,
84
- );
85
- }
86
-
87
- return Array.from(deduped.values());
88
- }
89
-
90
- export async function isTrustedWorkspacePath(
91
- workspacePath: string,
92
- provider: AgentKey,
93
- ): Promise<boolean> {
94
- const settings = await loadSettingsFile(globalSettingsPath());
95
- const normalizedWorkspacePath = resolve(workspacePath);
96
-
97
- return normalizeTrustedPaths(settings.trustedPaths).some((entry) =>
98
- entry.provider === provider && entry.workspacePath === normalizedWorkspacePath
99
- );
100
- }
101
-
102
- export async function upsertTrustedWorkspacePath(
103
- workspacePath: string,
104
- provider: AgentKey,
105
- ): Promise<void> {
106
- try {
107
- const settings = await loadSettingsFile(globalSettingsPath());
108
- settings.trustedPaths = normalizeTrustedPaths([
109
- ...(settings.trustedPaths ?? []),
110
- { workspacePath, provider },
111
- ]);
112
- await writeGlobalSettings(settings);
113
- } catch (e) {
114
- console.warn(`[settings] failed to upsert trusted path: ${errorMessage(e)}`);
115
- }
116
- }
117
-
118
55
  /**
119
56
  * Set telemetry enabled/disabled in global settings.
120
57
  */
@@ -4,20 +4,10 @@
4
4
  * Copies bundled agent skills from the installed package into the
5
5
  * provider-native global skill roots, mirroring the merge-copy approach
6
6
  * used by {@link installGlobalAgents} for agent configs.
7
- *
8
- * Previously this ran `npx skills add <repo>` at runtime, which cloned
9
- * the entire git repo on every version bump. Now the skills ship inside
10
- * the npm package (`.agents/skills/`) and are copied locally — no network
11
- * required, no `npx`/`bunx` dependency.
12
- *
13
- * SCM-variant skills (gh-commit, gh-create-pr, sl-commit, sl-submit-diff)
14
- * are excluded from the global install; `atomic init` installs them
15
- * per-project based on the user's selected SCM + active agent.
16
7
  */
17
8
 
18
9
  import { join } from "node:path";
19
10
  import { homedir } from "node:os";
20
- import { ALL_SCM_SKILLS } from "../config/index.ts";
21
11
  import { createCommonIgnoreFilter } from "../../lib/common-ignore.ts";
22
12
  import { copyDir, pathExists } from "./copy.ts";
23
13
 
@@ -47,12 +37,8 @@ const SKILL_DEST_DIRS = [
47
37
  ".claude/skills",
48
38
  ] as const;
49
39
 
50
- /** The set of SCM skill names to exclude from global installation. */
51
- const SCM_SKILL_SET = new Set<string>(ALL_SCM_SKILLS);
52
-
53
40
  /**
54
- * Copy bundled skills to the global skill directories, excluding
55
- * SCM-variant skills that are installed per-project by `atomic init`.
41
+ * Copy bundled skills to the global skill directories.
56
42
  */
57
43
  export async function installGlobalSkills(): Promise<void> {
58
44
  const src = join(packageRoot(), ".agents", "skills");
@@ -62,14 +48,11 @@ export async function installGlobalSkills(): Promise<void> {
62
48
  }
63
49
 
64
50
  const home = homeRoot();
65
-
66
- // Build the exclusion list from SCM skill names
67
- const exclude = [...SCM_SKILL_SET];
68
51
  const ignoreFilter = createCommonIgnoreFilter();
69
52
 
70
53
  await Promise.all(
71
54
  SKILL_DEST_DIRS.map((rel) =>
72
- copyDir(src, join(home, rel), { exclude, ignoreFilter }),
55
+ copyDir(src, join(home, rel), { ignoreFilter }),
73
56
  ),
74
57
  );
75
58
  }
@@ -1,175 +0,0 @@
1
- import { join } from "node:path";
2
- import { readdir } from "node:fs/promises";
3
- import { copyDir, pathExists, ensureDir } from "../../../services/system/copy.ts";
4
- import { createCommonIgnoreFilter } from "../../../lib/common-ignore.ts";
5
- import {
6
- SCM_SKILLS_BY_TYPE,
7
- type AgentKey,
8
- type SourceControlType,
9
- } from "../../../services/config/index.ts";
10
-
11
- export const SCM_PREFIX_BY_TYPE: Record<SourceControlType, "gh-" | "sl-"> = {
12
- github: "gh-",
13
- sapling: "sl-",
14
- };
15
-
16
- export function getScmPrefix(scmType: SourceControlType): "gh-" | "sl-" {
17
- return SCM_PREFIX_BY_TYPE[scmType];
18
- }
19
-
20
- export function isManagedScmEntry(name: string): boolean {
21
- return name.startsWith("gh-") || name.startsWith("sl-");
22
- }
23
-
24
- export interface ReconcileScmVariantsOptions {
25
- scmType: SourceControlType;
26
- agentFolder: string;
27
- skillsSubfolder: string;
28
- targetDir: string;
29
- configRoot: string;
30
- }
31
-
32
- export async function reconcileScmVariants(options: ReconcileScmVariantsOptions): Promise<void> {
33
- const { agentFolder, skillsSubfolder, targetDir, configRoot } = options;
34
- const srcDir = join(configRoot, agentFolder, skillsSubfolder);
35
- const destDir = join(targetDir, agentFolder, skillsSubfolder);
36
-
37
- if (!(await pathExists(srcDir)) || !(await pathExists(destDir))) {
38
- return;
39
- }
40
-
41
- const sourceEntries = await readdir(srcDir, { withFileTypes: true });
42
- const managedEntries = sourceEntries.filter((entry) => isManagedScmEntry(entry.name));
43
-
44
- if (process.env.DEBUG === "1" && managedEntries.length > 0) {
45
- console.log(
46
- `[DEBUG] Preserving existing managed SCM variants in ${destDir}: ${managedEntries
47
- .map((entry) => entry.name)
48
- .join(", ")}`
49
- );
50
- }
51
- }
52
-
53
- export interface SyncProjectScmSkillsOptions {
54
- scmType: SourceControlType;
55
- sourceSkillsDir: string;
56
- targetSkillsDir: string;
57
- }
58
-
59
- export async function syncProjectScmSkills(options: SyncProjectScmSkillsOptions): Promise<number> {
60
- const { scmType, sourceSkillsDir, targetSkillsDir } = options;
61
- const selectedPrefix = getScmPrefix(scmType);
62
-
63
- if (!(await pathExists(sourceSkillsDir))) {
64
- return 0;
65
- }
66
-
67
- await ensureDir(targetSkillsDir);
68
-
69
- const entries = await readdir(sourceSkillsDir, { withFileTypes: true });
70
- let copiedCount = 0;
71
-
72
- for (const entry of entries) {
73
- if (!entry.isDirectory()) continue;
74
- if (!entry.name.startsWith(selectedPrefix)) continue;
75
-
76
- const srcPath = join(sourceSkillsDir, entry.name);
77
- const destPath = join(targetSkillsDir, entry.name);
78
- await copyDir(srcPath, destPath, { ignoreFilter: createCommonIgnoreFilter() });
79
- copiedCount += 1;
80
- }
81
-
82
- return copiedCount;
83
- }
84
-
85
- /** Skills-CLI agent identifiers (match `bunx skills -a <value>`). */
86
- const SKILLS_AGENT_BY_KEY: Record<AgentKey, string> = {
87
- claude: "claude-code",
88
- opencode: "opencode",
89
- copilot: "github-copilot",
90
- };
91
-
92
- const SKILLS_REPO = "https://github.com/flora131/atomic.git";
93
-
94
- export interface InstallLocalScmSkillsOptions {
95
- scmType: SourceControlType;
96
- agentKey: AgentKey;
97
- /** The directory to run `bunx skills add` in (the project root). */
98
- cwd: string;
99
- }
100
-
101
- export interface InstallLocalScmSkillsResult {
102
- success: boolean;
103
- /** The explicit skill names that were requested (e.g. `["gh-commit", "gh-create-pr"]`). */
104
- skills: readonly string[];
105
- /** Non-empty when `success` is false. */
106
- details: string;
107
- }
108
-
109
- /**
110
- * Install the SCM skill variants (e.g. `gh-commit`, `gh-create-pr` for
111
- * GitHub) locally into the current project via `bunx skills add`. The `-g`
112
- * flag is intentionally omitted so the skills are installed per-project
113
- * (in the given `cwd`).
114
- *
115
- * Each skill is passed explicitly with `--skill <name>` — the skills CLI
116
- * does not support glob patterns like `gh-*`, which would either fail or
117
- * fall back to installing the entire skill set.
118
- *
119
- * This is best-effort: callers should treat a failed result as a warning,
120
- * not as a fatal error.
121
- */
122
- export async function installLocalScmSkills(
123
- options: InstallLocalScmSkillsOptions,
124
- ): Promise<InstallLocalScmSkillsResult> {
125
- const { scmType, agentKey, cwd } = options;
126
-
127
- const skills = SCM_SKILLS_BY_TYPE[scmType];
128
-
129
- const bunxPath = Bun.which("bunx");
130
- if (!bunxPath) {
131
- return { success: false, skills, details: "bunx not found on PATH" };
132
- }
133
-
134
- const agentFlag = SKILLS_AGENT_BY_KEY[agentKey];
135
- const skillFlags = skills.flatMap((skill) => ["--skill", skill]);
136
-
137
- try {
138
- const proc = Bun.spawn({
139
- cmd: [
140
- bunxPath,
141
- "skills",
142
- "add",
143
- SKILLS_REPO,
144
- ...skillFlags,
145
- "-a",
146
- agentFlag,
147
- "-y",
148
- ],
149
- cwd,
150
- stdout: "pipe",
151
- stderr: "pipe",
152
- env: process.env,
153
- });
154
- const [stderr, stdout, exitCode] = await Promise.all([
155
- new Response(proc.stderr).text(),
156
- new Response(proc.stdout).text(),
157
- proc.exited,
158
- ]);
159
- if (exitCode === 0) {
160
- return { success: true, skills, details: "" };
161
- }
162
- const details = stderr.trim().length > 0 ? stderr.trim() : stdout.trim();
163
- return {
164
- success: false,
165
- skills,
166
- details: details || `exit code ${exitCode}`,
167
- };
168
- } catch (error) {
169
- return {
170
- success: false,
171
- skills,
172
- details: error instanceof Error ? error.message : String(error),
173
- };
174
- }
175
- }