@bastani/atomic 0.8.1-1 → 0.8.2-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 (149) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/builtin/intercom/config.ts +3 -4
  3. package/dist/builtin/intercom/index.ts +6 -6
  4. package/dist/builtin/intercom/package.json +1 -1
  5. package/dist/builtin/mcp/agent-dir.ts +11 -2
  6. package/dist/builtin/mcp/cli.js +12 -6
  7. package/dist/builtin/mcp/config.ts +31 -22
  8. package/dist/builtin/mcp/package.json +1 -1
  9. package/dist/builtin/subagents/package.json +1 -1
  10. package/dist/builtin/subagents/src/agents/agents.ts +63 -23
  11. package/dist/builtin/subagents/src/agents/skills.ts +21 -21
  12. package/dist/builtin/subagents/src/extension/index.ts +9 -8
  13. package/dist/builtin/subagents/src/runs/shared/run-history.ts +13 -10
  14. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +3 -3
  15. package/dist/builtin/subagents/src/shared/artifacts.ts +18 -17
  16. package/dist/builtin/subagents/src/shared/types.ts +4 -4
  17. package/dist/builtin/web-access/config-paths.ts +11 -0
  18. package/dist/builtin/web-access/exa.ts +3 -2
  19. package/dist/builtin/web-access/gemini-api.ts +2 -1
  20. package/dist/builtin/web-access/gemini-search.ts +2 -1
  21. package/dist/builtin/web-access/gemini-web-config.ts +2 -1
  22. package/dist/builtin/web-access/github-extract.ts +2 -1
  23. package/dist/builtin/web-access/index.ts +11 -8
  24. package/dist/builtin/web-access/package.json +1 -1
  25. package/dist/builtin/web-access/perplexity.ts +2 -1
  26. package/dist/builtin/web-access/video-extract.ts +2 -1
  27. package/dist/builtin/web-access/youtube-extract.ts +2 -1
  28. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +4 -0
  29. package/dist/builtin/workflows/builtin/open-claude-design.ts +39 -22
  30. package/dist/builtin/workflows/builtin/ralph.ts +7 -0
  31. package/dist/builtin/workflows/package.json +1 -1
  32. package/dist/builtin/workflows/skills/workflow/SKILL.md +28 -20
  33. package/dist/builtin/workflows/skills/workflow/references/design-checklist.md +8 -4
  34. package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +52 -23
  35. package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +41 -12
  36. package/dist/builtin/workflows/src/extension/config-loader.ts +13 -14
  37. package/dist/builtin/workflows/src/extension/discovery.ts +4 -6
  38. package/dist/builtin/workflows/src/extension/index.ts +675 -524
  39. package/dist/builtin/workflows/src/extension/runtime.ts +40 -16
  40. package/dist/builtin/workflows/src/extension/wiring.ts +3 -0
  41. package/dist/builtin/workflows/src/extension/workflow-schema.ts +43 -33
  42. package/dist/builtin/workflows/src/runs/foreground/executor.ts +34 -10
  43. package/dist/builtin/workflows/src/shared/types.ts +1 -5
  44. package/dist/builtin/workflows/src/tui/graph-view.ts +245 -75
  45. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +23 -0
  46. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +259 -149
  47. package/dist/builtin/workflows/src/tui/status-helpers.ts +3 -3
  48. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +99 -10
  49. package/dist/builtin/workflows/src/tui/switcher.ts +4 -5
  50. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +29 -0
  51. package/dist/cli/args.d.ts.map +1 -1
  52. package/dist/cli/args.js +11 -8
  53. package/dist/cli/args.js.map +1 -1
  54. package/dist/config.d.ts +21 -0
  55. package/dist/config.d.ts.map +1 -1
  56. package/dist/config.js +59 -4
  57. package/dist/config.js.map +1 -1
  58. package/dist/core/agent-session.d.ts +1 -1
  59. package/dist/core/agent-session.d.ts.map +1 -1
  60. package/dist/core/agent-session.js +2 -2
  61. package/dist/core/agent-session.js.map +1 -1
  62. package/dist/core/auth-storage.d.ts +3 -1
  63. package/dist/core/auth-storage.d.ts.map +1 -1
  64. package/dist/core/auth-storage.js +31 -8
  65. package/dist/core/auth-storage.js.map +1 -1
  66. package/dist/core/extensions/runner.d.ts.map +1 -1
  67. package/dist/core/extensions/runner.js +9 -0
  68. package/dist/core/extensions/runner.js.map +1 -1
  69. package/dist/core/extensions/types.d.ts +11 -0
  70. package/dist/core/extensions/types.d.ts.map +1 -1
  71. package/dist/core/extensions/types.js.map +1 -1
  72. package/dist/core/model-registry.d.ts +3 -2
  73. package/dist/core/model-registry.d.ts.map +1 -1
  74. package/dist/core/model-registry.js +25 -8
  75. package/dist/core/model-registry.js.map +1 -1
  76. package/dist/core/package-manager.d.ts +3 -0
  77. package/dist/core/package-manager.d.ts.map +1 -1
  78. package/dist/core/package-manager.js +97 -58
  79. package/dist/core/package-manager.js.map +1 -1
  80. package/dist/core/resource-loader.d.ts +1 -0
  81. package/dist/core/resource-loader.d.ts.map +1 -1
  82. package/dist/core/resource-loader.js +37 -36
  83. package/dist/core/resource-loader.js.map +1 -1
  84. package/dist/core/sdk.d.ts +5 -4
  85. package/dist/core/sdk.d.ts.map +1 -1
  86. package/dist/core/sdk.js +2 -2
  87. package/dist/core/sdk.js.map +1 -1
  88. package/dist/core/settings-manager.d.ts +7 -1
  89. package/dist/core/settings-manager.d.ts.map +1 -1
  90. package/dist/core/settings-manager.js +29 -8
  91. package/dist/core/settings-manager.js.map +1 -1
  92. package/dist/core/system-prompt.d.ts +1 -1
  93. package/dist/core/system-prompt.d.ts.map +1 -1
  94. package/dist/core/system-prompt.js.map +1 -1
  95. package/dist/core/telemetry.d.ts.map +1 -1
  96. package/dist/core/telemetry.js +2 -2
  97. package/dist/core/telemetry.js.map +1 -1
  98. package/dist/core/timings.d.ts.map +1 -1
  99. package/dist/core/timings.js +2 -2
  100. package/dist/core/timings.js.map +1 -1
  101. package/dist/core/tools/index.d.ts +1 -0
  102. package/dist/core/tools/index.d.ts.map +1 -1
  103. package/dist/core/tools/index.js +8 -0
  104. package/dist/core/tools/index.js.map +1 -1
  105. package/dist/core/tools/todos.d.ts.map +1 -1
  106. package/dist/core/tools/todos.js +3 -3
  107. package/dist/core/tools/todos.js.map +1 -1
  108. package/dist/index.d.ts +2 -2
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +2 -2
  111. package/dist/index.js.map +1 -1
  112. package/dist/main.d.ts.map +1 -1
  113. package/dist/main.js +6 -6
  114. package/dist/main.js.map +1 -1
  115. package/dist/modes/interactive/components/atomic-banner.d.ts +4 -0
  116. package/dist/modes/interactive/components/atomic-banner.d.ts.map +1 -0
  117. package/dist/modes/interactive/components/atomic-banner.js +34 -0
  118. package/dist/modes/interactive/components/atomic-banner.js.map +1 -0
  119. package/dist/modes/interactive/components/chat-message-renderer.d.ts +99 -0
  120. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -0
  121. package/dist/modes/interactive/components/chat-message-renderer.js +450 -0
  122. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -0
  123. package/dist/modes/interactive/components/chat-transcript.d.ts +69 -0
  124. package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -0
  125. package/dist/modes/interactive/components/chat-transcript.js +183 -0
  126. package/dist/modes/interactive/components/chat-transcript.js.map +1 -0
  127. package/dist/modes/interactive/components/footer.d.ts +16 -4
  128. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  129. package/dist/modes/interactive/components/footer.js +110 -137
  130. package/dist/modes/interactive/components/footer.js.map +1 -1
  131. package/dist/modes/interactive/components/index.d.ts +2 -0
  132. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  133. package/dist/modes/interactive/components/index.js +2 -0
  134. package/dist/modes/interactive/components/index.js.map +1 -1
  135. package/dist/modes/interactive/interactive-mode.d.ts +9 -0
  136. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  137. package/dist/modes/interactive/interactive-mode.js +192 -137
  138. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  139. package/dist/modes/interactive/theme/catppuccin-mocha.json +5 -5
  140. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  141. package/dist/modes/rpc/rpc-mode.js +11 -0
  142. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  143. package/dist/utils/tools-manager.d.ts.map +1 -1
  144. package/dist/utils/tools-manager.js +2 -2
  145. package/dist/utils/tools-manager.js.map +1 -1
  146. package/dist/utils/version-check.d.ts.map +1 -1
  147. package/dist/utils/version-check.js +2 -2
  148. package/dist/utils/version-check.js.map +1 -1
  149. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { APP_NAME } from "@bastani/atomic";
2
+ import { APP_NAME, getEnvValue } from "@bastani/atomic";
3
3
 
4
4
  const ENV_PREFIX = APP_NAME.toUpperCase();
5
5
  const SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV = `${ENV_PREFIX}_SUBAGENT_INHERIT_PROJECT_CONTEXT`;
@@ -28,7 +28,7 @@ const SKILLS_HEADER = "\n\nThe following skills provide specialized instructions
28
28
  const DATE_HEADER = "\nCurrent date:";
29
29
 
30
30
  function readBooleanEnv(name: string): boolean | undefined {
31
- const value = process.env[name];
31
+ const value = getEnvValue(name);
32
32
  if (value === undefined) return undefined;
33
33
  return value !== "0";
34
34
  }
@@ -134,7 +134,7 @@ export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
134
134
  });
135
135
 
136
136
  pi.on("before_agent_start", async (event) => {
137
- const intercomSessionName = process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV]?.trim();
137
+ const intercomSessionName = getEnvValue(SUBAGENT_INTERCOM_SESSION_NAME_ENV)?.trim();
138
138
  if (intercomSessionName && typeof pi.setSessionName === "function") {
139
139
  pi.setSessionName(intercomSessionName);
140
140
  }
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { CONFIG_DIR_NAME } from "@bastani/atomic";
4
+ import { getAgentConfigPaths } from "@bastani/atomic";
5
5
  import { TEMP_ARTIFACTS_DIR, type ArtifactPaths } from "./types.ts";
6
6
  const CLEANUP_MARKER_FILE = ".last-cleanup";
7
7
 
@@ -75,25 +75,26 @@ export function cleanupOldArtifacts(dir: string, maxAgeDays: number): void {
75
75
  export function cleanupAllArtifactDirs(maxAgeDays: number): void {
76
76
  cleanupOldArtifacts(TEMP_ARTIFACTS_DIR, maxAgeDays);
77
77
 
78
- const sessionsBase = path.join(os.homedir(), CONFIG_DIR_NAME, "agent", "sessions");
79
- if (!fs.existsSync(sessionsBase)) return;
78
+ for (const sessionsBase of getAgentConfigPaths("sessions")) {
79
+ if (!fs.existsSync(sessionsBase)) continue;
80
80
 
81
- let dirs: string[];
82
- try {
83
- dirs = fs.readdirSync(sessionsBase);
84
- } catch {
85
- // Session artifact cleanup is best-effort. If the sessions root cannot be read,
86
- // skip cleanup instead of failing extension startup.
87
- return;
88
- }
89
-
90
- for (const dir of dirs) {
91
- const artifactsDir = path.join(sessionsBase, dir, "subagent-artifacts");
81
+ let dirs: string[];
92
82
  try {
93
- cleanupOldArtifacts(artifactsDir, maxAgeDays);
83
+ dirs = fs.readdirSync(sessionsBase);
94
84
  } catch {
95
- // Session cleanup is best-effort. Keep going so one unreadable session dir
96
- // does not block cleanup for the rest.
85
+ // Session artifact cleanup is best-effort. If the sessions root cannot be read,
86
+ // skip cleanup instead of failing extension startup.
87
+ continue;
88
+ }
89
+
90
+ for (const dir of dirs) {
91
+ const artifactsDir = path.join(sessionsBase, dir, "subagent-artifacts");
92
+ try {
93
+ cleanupOldArtifacts(artifactsDir, maxAgeDays);
94
+ } catch {
95
+ // Session cleanup is best-effort. Keep going so one unreadable session dir
96
+ // does not block cleanup for the rest.
97
+ }
97
98
  }
98
99
  }
99
100
  }
@@ -7,7 +7,7 @@ import * as path from "node:path";
7
7
  import type { Message } from "@earendil-works/pi-ai";
8
8
  import type { FSWatcher } from "node:fs";
9
9
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
10
- import { APP_NAME } from "@bastani/atomic";
10
+ import { APP_NAME, getEnvValue } from "@bastani/atomic";
11
11
 
12
12
  const ENV_PREFIX = APP_NAME.toUpperCase();
13
13
  const SUBAGENT_MAX_DEPTH_ENV = `${ENV_PREFIX}_SUBAGENT_MAX_DEPTH`;
@@ -650,7 +650,7 @@ export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
650
650
  }
651
651
 
652
652
  export function resolveCurrentMaxSubagentDepth(configMaxDepth?: number): number {
653
- return normalizeMaxSubagentDepth(process.env[SUBAGENT_MAX_DEPTH_ENV])
653
+ return normalizeMaxSubagentDepth(getEnvValue(SUBAGENT_MAX_DEPTH_ENV))
654
654
  ?? normalizeMaxSubagentDepth(configMaxDepth)
655
655
  ?? DEFAULT_SUBAGENT_MAX_DEPTH;
656
656
  }
@@ -662,14 +662,14 @@ export function resolveChildMaxSubagentDepth(parentMaxDepth: number, agentMaxDep
662
662
  }
663
663
 
664
664
  export function checkSubagentDepth(configMaxDepth?: number): { blocked: boolean; depth: number; maxDepth: number } {
665
- const depth = Number(process.env[SUBAGENT_DEPTH_ENV] ?? "0");
665
+ const depth = Number(getEnvValue(SUBAGENT_DEPTH_ENV) ?? "0");
666
666
  const maxDepth = resolveCurrentMaxSubagentDepth(configMaxDepth);
667
667
  const blocked = Number.isFinite(depth) && depth >= maxDepth;
668
668
  return { blocked, depth, maxDepth };
669
669
  }
670
670
 
671
671
  export function getSubagentDepthEnv(maxDepth?: number): Record<string, string> {
672
- const parentDepth = Number(process.env[SUBAGENT_DEPTH_ENV] ?? "0");
672
+ const parentDepth = Number(getEnvValue(SUBAGENT_DEPTH_ENV) ?? "0");
673
673
  const nextDepth = Number.isFinite(parentDepth) ? parentDepth + 1 : 1;
674
674
  return {
675
675
  [SUBAGENT_DEPTH_ENV]: String(nextDepth),
@@ -0,0 +1,11 @@
1
+ import { existsSync } from "node:fs";
2
+ import { getUserConfigPaths } from "@bastani/atomic";
3
+
4
+ export const WEB_SEARCH_CONFIG_PATHS = getUserConfigPaths("web-search.json");
5
+ export const WEB_SEARCH_CONFIG_PATH = WEB_SEARCH_CONFIG_PATHS[0] ?? "~/.atomic/web-search.json";
6
+ export const EXA_USAGE_PATHS = getUserConfigPaths("exa-usage.json");
7
+ export const EXA_USAGE_PATH = EXA_USAGE_PATHS[0] ?? "~/.atomic/exa-usage.json";
8
+
9
+ export function findReadableConfigPath(paths = WEB_SEARCH_CONFIG_PATHS): string {
10
+ return paths.find((path) => existsSync(path)) ?? paths[0] ?? WEB_SEARCH_CONFIG_PATH;
11
+ }
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { CONFIG_DIR_NAME } from "@bastani/atomic";
5
+ import { EXA_USAGE_PATH, findReadableConfigPath } from "./config-paths.ts";
5
6
  import { activityMonitor } from "./activity.js";
6
7
  import type { ExtractedContent } from "./extract.js";
7
8
  import type { SearchOptions, SearchResponse } from "./perplexity.js";
@@ -9,8 +10,8 @@ import type { SearchOptions, SearchResponse } from "./perplexity.js";
9
10
  const EXA_ANSWER_URL = "https://api.exa.ai/answer";
10
11
  const EXA_SEARCH_URL = "https://api.exa.ai/search";
11
12
  const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
12
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
13
- const USAGE_PATH = join(homedir(), CONFIG_DIR_NAME, "exa-usage.json");
13
+ const CONFIG_PATH = findReadableConfigPath();
14
+ const USAGE_PATH = EXA_USAGE_PATH;
14
15
 
15
16
  const MONTHLY_LIMIT = 1000;
16
17
  const WARNING_THRESHOLD = 800;
@@ -2,9 +2,10 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { CONFIG_DIR_NAME } from "@bastani/atomic";
5
+ import { findReadableConfigPath } from "./config-paths.ts";
5
6
 
6
7
  export const API_BASE = "https://generativelanguage.googleapis.com/v1beta";
7
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
8
+ const CONFIG_PATH = findReadableConfigPath();
8
9
  export const DEFAULT_MODEL = "gemini-3-flash-preview";
9
10
 
10
11
  interface GeminiApiConfig {
@@ -7,6 +7,7 @@ import { getApiKey, API_BASE, DEFAULT_MODEL } from "./gemini-api.js";
7
7
  import { isGeminiWebAvailable, queryWithCookies } from "./gemini-web.js";
8
8
  import { isPerplexityAvailable, searchWithPerplexity, type SearchResult, type SearchResponse, type SearchOptions } from "./perplexity.js";
9
9
  import { hasExaApiKey, isExaAvailable, searchWithExa } from "./exa.js";
10
+ import { findReadableConfigPath } from "./config-paths.ts";
10
11
 
11
12
  export type SearchProvider = "auto" | "perplexity" | "gemini" | "exa";
12
13
  export type ResolvedSearchProvider = Exclude<SearchProvider, "auto">;
@@ -15,7 +16,7 @@ export interface AttributedSearchResponse extends SearchResponse {
15
16
  provider: ResolvedSearchProvider;
16
17
  }
17
18
 
18
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
19
+ const CONFIG_PATH = findReadableConfigPath();
19
20
 
20
21
  let cachedSearchConfig: { searchProvider: SearchProvider; searchModel?: string } | null = null;
21
22
 
@@ -2,8 +2,9 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { APP_NAME, CONFIG_DIR_NAME } from "@bastani/atomic";
5
+ import { findReadableConfigPath } from "./config-paths.ts";
5
6
 
6
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
7
+ const CONFIG_PATH = findReadableConfigPath();
7
8
  const ALLOW_BROWSER_COOKIES_ENV = `${APP_NAME.toUpperCase()}_ALLOW_BROWSER_COOKIES`;
8
9
 
9
10
  interface GeminiWebConfig {
@@ -6,8 +6,9 @@ import { CONFIG_DIR_NAME } from "@bastani/atomic";
6
6
  import { activityMonitor } from "./activity.js";
7
7
  import type { ExtractedContent } from "./extract.js";
8
8
  import { checkGhAvailable, checkRepoSize, fetchViaApi, showGhHint } from "./github-api.js";
9
+ import { findReadableConfigPath } from "./config-paths.ts";
9
10
 
10
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
11
+ const CONFIG_PATH = findReadableConfigPath();
11
12
 
12
13
  const BINARY_EXTENSIONS = new Set([
13
14
  ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".webp", ".svg", ".tiff", ".tif",
@@ -33,14 +33,16 @@ import { createRequire } from "node:module";
33
33
  import { platform, homedir } from "node:os";
34
34
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
35
35
  import { join } from "node:path";
36
- import { CONFIG_DIR_NAME } from "@bastani/atomic";
36
+ import { CONFIG_DIR_NAME, getUserConfigPaths } from "@bastani/atomic";
37
+ import { findReadableConfigPath } from "./config-paths.ts";
37
38
  import { isPerplexityAvailable } from "./perplexity.js";
38
39
  import { isExaAvailable } from "./exa.js";
39
40
  import { isGeminiApiAvailable } from "./gemini-api.js";
40
41
  import { getActiveGoogleEmail, isGeminiWebAvailable } from "./gemini-web.js";
41
42
  import { isBrowserCookieAccessAllowed } from "./gemini-web-config.ts";
42
43
 
43
- const WEB_SEARCH_CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
44
+ const WEB_SEARCH_CONFIG_PATH = getUserConfigPaths("web-search.json")[0] ?? join(homedir(), CONFIG_DIR_NAME, "web-search.json");
45
+ const WEB_SEARCH_CONFIG_READ_PATH = findReadableConfigPath();
44
46
 
45
47
  interface WebSearchConfig {
46
48
  provider?: string;
@@ -69,25 +71,26 @@ interface CuratorBootstrap {
69
71
  }
70
72
 
71
73
  function loadConfig(): WebSearchConfig {
72
- if (!existsSync(WEB_SEARCH_CONFIG_PATH)) return {};
73
- const raw = readFileSync(WEB_SEARCH_CONFIG_PATH, "utf-8");
74
+ if (!existsSync(WEB_SEARCH_CONFIG_READ_PATH)) return {};
75
+ const raw = readFileSync(WEB_SEARCH_CONFIG_READ_PATH, "utf-8");
74
76
  try {
75
77
  return JSON.parse(raw) as WebSearchConfig;
76
78
  } catch (err) {
77
79
  const message = err instanceof Error ? err.message : String(err);
78
- throw new Error(`Failed to parse ${WEB_SEARCH_CONFIG_PATH}: ${message}`);
80
+ throw new Error(`Failed to parse ${WEB_SEARCH_CONFIG_READ_PATH}: ${message}`);
79
81
  }
80
82
  }
81
83
 
82
84
  function saveConfig(updates: Partial<WebSearchConfig>): void {
83
85
  let config: Record<string, unknown> = {};
84
- if (existsSync(WEB_SEARCH_CONFIG_PATH)) {
85
- const raw = readFileSync(WEB_SEARCH_CONFIG_PATH, "utf-8");
86
+ const existingConfigPath = findReadableConfigPath();
87
+ if (existsSync(existingConfigPath)) {
88
+ const raw = readFileSync(existingConfigPath, "utf-8");
86
89
  try {
87
90
  config = JSON.parse(raw) as Record<string, unknown>;
88
91
  } catch (err) {
89
92
  const message = err instanceof Error ? err.message : String(err);
90
- throw new Error(`Failed to parse ${WEB_SEARCH_CONFIG_PATH}: ${message}`);
93
+ throw new Error(`Failed to parse ${existingConfigPath}: ${message}`);
91
94
  }
92
95
  }
93
96
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.8.1-1",
3
+ "version": "0.8.2-0",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction.",
6
6
  "contributors": [
@@ -4,9 +4,10 @@ import { join } from "node:path";
4
4
  import { CONFIG_DIR_NAME } from "@bastani/atomic";
5
5
  import { activityMonitor } from "./activity.js";
6
6
  import type { ExtractedContent } from "./extract.js";
7
+ import { findReadableConfigPath } from "./config-paths.ts";
7
8
 
8
9
  const PERPLEXITY_API_URL = "https://api.perplexity.ai/chat/completions";
9
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
10
+ const CONFIG_PATH = findReadableConfigPath();
10
11
 
11
12
  const RATE_LIMIT = {
12
13
  maxRequests: 10,
@@ -9,8 +9,9 @@ import { isGeminiWebAvailable, queryWithCookies } from "./gemini-web.js";
9
9
  import { queryGeminiApiWithVideo, getApiKey, API_BASE } from "./gemini-api.js";
10
10
  import { extractHeadingTitle, type ExtractedContent, type ExtractOptions, type FrameResult } from "./extract.js";
11
11
  import { readExecError, trimErrorText, mapFfmpegError } from "./utils.js";
12
+ import { findReadableConfigPath } from "./config-paths.ts";
12
13
 
13
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
14
+ const CONFIG_PATH = findReadableConfigPath();
14
15
  const UPLOAD_BASE = "https://generativelanguage.googleapis.com/upload/v1beta";
15
16
 
16
17
  const DEFAULT_VIDEO_PROMPT = `Extract the complete content of this video. Include:
@@ -9,8 +9,9 @@ import { isGeminiApiAvailable, queryGeminiApiWithVideo } from "./gemini-api.js";
9
9
  import { searchWithPerplexity } from "./perplexity.js";
10
10
  import { extractHeadingTitle, type ExtractedContent, type FrameResult, type VideoFrame } from "./extract.js";
11
11
  import { formatSeconds, readExecError, isTimeoutError, trimErrorText, mapFfmpegError } from "./utils.js";
12
+ import { findReadableConfigPath } from "./config-paths.ts";
12
13
 
13
- const CONFIG_PATH = join(homedir(), CONFIG_DIR_NAME, "web-search.json");
14
+ const CONFIG_PATH = findReadableConfigPath();
14
15
 
15
16
  const YOUTUBE_PROMPT = `Extract the complete content of this YouTube video. Include:
16
17
  1. Video title, channel name, and duration
@@ -185,6 +185,8 @@ export default defineWorkflow("deep-research-codebase")
185
185
  codebaseLines,
186
186
  );
187
187
 
188
+ let noAskQuestionToolSet = ["read, bash, edit, write, todo"];
189
+
188
190
  let plannerModelConfig = {
189
191
  model: "openai/gpt-5.5",
190
192
  fallbackModels: [
@@ -193,6 +195,7 @@ export default defineWorkflow("deep-research-codebase")
193
195
  "github-copilot/claude-opus-4.7",
194
196
  ],
195
197
  thinkingLevel: "high" as const,
198
+ tools: noAskQuestionToolSet,
196
199
  };
197
200
 
198
201
  let explorerModelConfig = {
@@ -203,6 +206,7 @@ export default defineWorkflow("deep-research-codebase")
203
206
  "github-copilot/claude-haiku-4.5",
204
207
  ],
205
208
  thinkingLevel: "low" as const,
209
+ tools: noAskQuestionToolSet,
206
210
  };
207
211
 
208
212
  const initialDiscovery = await ctx.parallel(
@@ -122,8 +122,8 @@ function prepareArtifactDir(): {
122
122
  } {
123
123
  const runId = `${new Date().toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
124
124
  const candidates = [
125
- join(process.cwd(), ".atomic", "workflows", "open-claude-design", runId),
126
- join(tmpdir(), "pi-open-claude-design", runId),
125
+ join(process.cwd(), "specs", "design", runId),
126
+ join(tmpdir(), "open-claude-design", runId),
127
127
  ];
128
128
  for (const candidate of candidates) {
129
129
  try {
@@ -164,6 +164,13 @@ const ANTI_SLOP_RULES = [
164
164
  "Commit to a specific aesthetic direction; do not hedge with generic SaaS defaults.",
165
165
  ].join("\n");
166
166
 
167
+ const PLAYWRIGHT_BROWSER_BOOTSTRAP_RULES = [
168
+ "Probe for playwright-cli availability with `playwright-cli --version` (or `npx --no-install playwright-cli --version` when relying on a project-local install). Do not install playwright-cli itself.",
169
+ "If playwright-cli is available but opening a page fails because Chrome, Chrome for Testing, Chromium, or another browser executable is not installed, run `playwright-cli install-browser chrome-for-testing` (or the equivalent `npx --no-install playwright-cli install-browser chrome-for-testing`) and retry the browser action once.",
170
+ "Only install the missing browser runtime; do not install npm packages, change project dependencies, or repeatedly retry failed installs.",
171
+ "If playwright-cli is unavailable or browser installation still fails, degrade gracefully and surface the manual file path / URL.",
172
+ ].join("\n");
173
+
167
174
  export default defineWorkflow("open-claude-design")
168
175
  .description(
169
176
  "AI-powered design workflow: design-system onboarding → reference import → HTML generation → impeccable-driven refinement → quality gate → rich HTML handoff. Each stage delegates to a specific impeccable sub-skill; the user can iteratively review and annotate the generated HTML through playwright-cli.",
@@ -447,13 +454,15 @@ export default defineWorkflow("open-claude-design")
447
454
  "impeccable_skill",
448
455
  "extract — separate one-off styling from repeated, intentional patterns. Only carry forward what is used 3+ times or what is structurally load-bearing.",
449
456
  ],
457
+ ["playwright_browser_bootstrap", PLAYWRIGHT_BROWSER_BOOTSTRAP_RULES],
450
458
  [
451
459
  "instructions",
452
460
  [
453
461
  "1. Use browser/screenshot tooling (e.g. playwright-cli) if available; cite observable evidence rather than guessing.",
454
- "2. Analyze: layout, visual hierarchy, navigation, color, typography, spacing, states, interactions, responsive behavior.",
455
- "3. Separate reference-specific styling from requirements that should transfer to this project's design system.",
456
- "4. If the URL is inaccessible, state that and provide a best-effort fallback based only on available information — never fabricate observations.",
462
+ "2. If playwright-cli is available but opening the reference URL reports a missing browser executable, install Chrome for Testing with the bootstrap command and retry once.",
463
+ "3. Analyze: layout, visual hierarchy, navigation, color, typography, spacing, states, interactions, responsive behavior.",
464
+ "4. Separate reference-specific styling from requirements that should transfer to this project's design system.",
465
+ "5. If the URL is inaccessible or browser bootstrap fails, state that and provide a best-effort fallback based only on available information — never fabricate observations.",
457
466
  ].join("\n"),
458
467
  ],
459
468
  [
@@ -573,14 +582,16 @@ export default defineWorkflow("open-claude-design")
573
582
  ],
574
583
  ["preview_path", previewPath],
575
584
  ["preview_file_url", previewFileUrl],
585
+ ["playwright_browser_bootstrap", PLAYWRIGHT_BROWSER_BOOTSTRAP_RULES],
576
586
  [
577
587
  "instructions",
578
588
  [
579
- "1. Probe for playwright-cli availability by running `playwright-cli --version` (or `npx --no-install playwright-cli --version`). Do not install anything.",
580
- `2. If available, run: \`playwright-cli open ${previewFileUrl}\` then \`playwright-cli show --annotate\` so the user can draw boxes and leave notes directly on the live page.`,
581
- "3. Once the user finishes annotating, capture the returned annotated snapshot path / notes and surface them in your output.",
582
- `4. If playwright-cli is NOT available, do not attempt installation. Instead, print a clear instruction block telling the user to open the file manually at: ${previewPath} (or via the URL ${previewFileUrl}).`,
583
- "5. Never block the workflow on unavailable tooling; always exit with a non-empty status string.",
589
+ "1. Probe for playwright-cli availability using the bootstrap rules above.",
590
+ `2. If available, run: \`playwright-cli open ${previewFileUrl}\`. If that reports a missing browser executable, install Chrome for Testing with the bootstrap command and retry once.`,
591
+ "3. Then run `playwright-cli show --annotate` so the user can draw boxes and leave notes directly on the live page.",
592
+ "4. Once the user finishes annotating, capture the returned annotated snapshot path / notes and surface them in your output.",
593
+ `5. If playwright-cli is NOT available or browser bootstrap fails, print a clear instruction block telling the user to open the file manually at: ${previewPath} (or via the URL ${previewFileUrl}).`,
594
+ "6. Never block the workflow on unavailable tooling; always exit with a non-empty status string.",
584
595
  ].join("\n"),
585
596
  ],
586
597
  [
@@ -699,13 +710,15 @@ export default defineWorkflow("open-claude-design")
699
710
  ["preview_path", previewPath],
700
711
  ["preview_file_url", previewFileUrl],
701
712
  ["current_design_and_feedback", "{previous}"],
713
+ ["playwright_browser_bootstrap", PLAYWRIGHT_BROWSER_BOOTSTRAP_RULES],
702
714
  [
703
715
  "instructions",
704
716
  [
705
- `1. Attempt rendering verification via playwright-cli: \`playwright-cli open ${previewFileUrl}\`, then \`playwright-cli resize 360 800\`, \`playwright-cli screenshot --filename=${join(artifactDir, `mobile-${iteration}.png`)}\`, \`playwright-cli resize 1440 900\`, \`playwright-cli screenshot --filename=${join(artifactDir, `desktop-${iteration}.png`)}\`.`,
706
- "2. Check: contrast (WCAG AA), overflow, spacing rhythm, alignment, breakpoint behavior, empty/loading/error states, keyboard/pointer affordances, focus rings, prefers-reduced-motion.",
707
- "3. If playwright-cli is unavailable, perform a static design review of the HTML source and mark every finding as `needs-rendering-verification`.",
708
- "4. Distinguish confirmed visual issues from risks that need rendering verification. Never fabricate rendered evidence.",
717
+ `1. Attempt rendering verification via playwright-cli: \`playwright-cli open ${previewFileUrl}\`. If that reports a missing browser executable, install Chrome for Testing with the bootstrap command and retry once.`,
718
+ `2. Then run \`playwright-cli resize 360 800\`, \`playwright-cli screenshot --filename=${join(artifactDir, `mobile-${iteration}.png`)}\`, \`playwright-cli resize 1440 900\`, \`playwright-cli screenshot --filename=${join(artifactDir, `desktop-${iteration}.png`)}\`.`,
719
+ "3. Check: contrast (WCAG AA), overflow, spacing rhythm, alignment, breakpoint behavior, empty/loading/error states, keyboard/pointer affordances, focus rings, prefers-reduced-motion.",
720
+ "4. If playwright-cli is unavailable or browser bootstrap fails, perform a static design review of the HTML source and mark every finding as `needs-rendering-verification`.",
721
+ "5. Distinguish confirmed visual issues from risks that need rendering verification. Never fabricate rendered evidence.",
709
722
  ].join("\n"),
710
723
  ],
711
724
  [
@@ -785,13 +798,15 @@ export default defineWorkflow("open-claude-design")
785
798
  ],
786
799
  ["preview_path", previewPath],
787
800
  ["preview_file_url", previewFileUrl],
801
+ ["playwright_browser_bootstrap", PLAYWRIGHT_BROWSER_BOOTSTRAP_RULES],
788
802
  [
789
803
  "instructions",
790
804
  [
791
- `1. If playwright-cli is available, run \`playwright-cli goto ${previewFileUrl}\` (or open if no session is active), then \`playwright-cli show --annotate\` to invite annotated feedback.`,
792
- `2. If playwright-cli is unavailable, surface the path clearly: ${previewPath} (URL: ${previewFileUrl}).`,
793
- "3. Return any captured annotations as structured notes the next user-feedback step can read.",
794
- "4. Do not block on unavailable tooling.",
805
+ `1. If playwright-cli is available, run \`playwright-cli goto ${previewFileUrl}\` (or \`playwright-cli open ${previewFileUrl}\` if no session is active). If that reports a missing browser executable, install Chrome for Testing with the bootstrap command and retry once.`,
806
+ "2. Then run `playwright-cli show --annotate` to invite annotated feedback.",
807
+ `3. If playwright-cli is unavailable or browser bootstrap fails, surface the path clearly: ${previewPath} (URL: ${previewFileUrl}).`,
808
+ "4. Return any captured annotations as structured notes the next user-feedback step can read.",
809
+ "5. Do not block on unavailable tooling.",
795
810
  ].join("\n"),
796
811
  ],
797
812
  [
@@ -948,13 +963,15 @@ export default defineWorkflow("open-claude-design")
948
963
  ["spec_file_url", specFileUrl],
949
964
  ["preview_path", previewPath],
950
965
  ["preview_file_url", previewFileUrl],
966
+ ["playwright_browser_bootstrap", PLAYWRIGHT_BROWSER_BOOTSTRAP_RULES],
951
967
  [
952
968
  "instructions",
953
969
  [
954
- "1. Probe for playwright-cli (`playwright-cli --version` or `npx --no-install playwright-cli --version`). Never attempt installation.",
955
- `2. If available, run \`playwright-cli open ${specFileUrl}\` then \`playwright-cli show --annotate\` so the user can capture any final notes.`,
956
- `3. Always print, prominently, the absolute paths so the user can open them manually:\n - Final spec: ${specPath}\n - Approved preview: ${previewPath}`,
957
- "4. Do not block the workflow; return a structured summary even if no tooling worked.",
970
+ "1. Probe for playwright-cli availability using the bootstrap rules above.",
971
+ `2. If available, run \`playwright-cli open ${specFileUrl}\`. If that reports a missing browser executable, install Chrome for Testing with the bootstrap command and retry once.`,
972
+ "3. Then run `playwright-cli show --annotate` so the user can capture any final notes.",
973
+ `4. Always print, prominently, the absolute paths so the user can open them manually:\n - Final spec: ${specPath}\n - Approved preview: ${previewPath}`,
974
+ "5. Do not block the workflow; return a structured summary even if no tooling worked.",
958
975
  ].join("\n"),
959
976
  ],
960
977
  [
@@ -152,6 +152,8 @@ export default defineWorkflow("ralph")
152
152
  let approved = false;
153
153
  let iterationsCompleted = 0;
154
154
 
155
+ let noAskQuestionToolSet = ["read, bash, edit, write, todo"];
156
+
155
157
  let plannerModelConfig = {
156
158
  model: "openai/gpt-5.5",
157
159
  fallbackModels: [
@@ -160,6 +162,7 @@ export default defineWorkflow("ralph")
160
162
  "github-copilot/claude-opus-4.7",
161
163
  ],
162
164
  thinkingLevel: "high" as const,
165
+ tools: noAskQuestionToolSet,
163
166
  };
164
167
 
165
168
  let orchestratorModelConfig = {
@@ -170,6 +173,7 @@ export default defineWorkflow("ralph")
170
173
  "github-copilot/claude-sonnet-4.6",
171
174
  ],
172
175
  thinkingLevel: "medium" as const,
176
+ tools: noAskQuestionToolSet,
173
177
  };
174
178
 
175
179
  let simplifierModelConfig = {
@@ -180,6 +184,7 @@ export default defineWorkflow("ralph")
180
184
  "github-copilot/claude-sonnet-4.6",
181
185
  ],
182
186
  thinkingLevel: "medium" as const,
187
+ tools: noAskQuestionToolSet,
183
188
  };
184
189
 
185
190
  let reviewerModelConfig = {
@@ -190,6 +195,7 @@ export default defineWorkflow("ralph")
190
195
  "github-copilot/claude-opus-4.7",
191
196
  ],
192
197
  thinkingLevel: "high" as const,
198
+ tools: noAskQuestionToolSet,
193
199
  };
194
200
 
195
201
  let explorerModelConfig = {
@@ -200,6 +206,7 @@ export default defineWorkflow("ralph")
200
206
  "github-copilot/claude-haiku-4.5",
201
207
  ],
202
208
  thinkingLevel: "low" as const,
209
+ tools: noAskQuestionToolSet,
203
210
  };
204
211
 
205
212
  for (let iteration = 1; iteration <= maxLoops; iteration += 1) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.8.1-1",
3
+ "version": "0.8.2-0",
4
4
  "private": true,
5
5
  "description": "pi extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [