@youtyan/code-viewer 0.6.7 → 0.6.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.
package/README.md CHANGED
@@ -34,6 +34,8 @@ Requires Node.js 20 or newer when installed from npm. Development uses
34
34
  viewer.
35
35
  - Browse SQLite, PostgreSQL, MySQL, Redis, Elasticsearch, and S3-compatible
36
36
  object storage (MinIO, LocalStack) with a built-in datastore viewer.
37
+ Local Supabase CLI (`supabase start`) Postgres projects are auto-discovered
38
+ too, without needing a `docker-compose.yml`.
37
39
  - Read the built-in Help page for getting started, the `.code-viewer/`
38
40
  project files, AI annotations, datastores, the agent skill, and
39
41
  keybindings.
@@ -42,8 +44,9 @@ Requires Node.js 20 or newer when installed from npm. Development uses
42
44
  `@youtyan/code-viewer` version and execution origin (npx cache vs
43
45
  local), SQLite driver and snapshot store, Git, discovery summary,
44
46
  per-source datastore connectivity (each discovered SQLite / docker
45
- SQL / Redis / Elasticsearch / S3 source gets one row with a 2s
46
- minimal-read probe; failure rows include a paste-safe retry hint),
47
+ SQL / Supabase CLI / Redis / Elasticsearch / S3 source gets one row
48
+ with a 2s minimal-read probe; failure rows include a paste-safe retry
49
+ hint),
47
50
  Docker / Compose health (config dry-parse + `compose ps` per service),
48
51
  and the listening port. Useful when `npx` cache mismatch (e.g.
49
52
  `NODE_MODULE_VERSION` errors) needs a remediation hint, or when a
@@ -178,7 +181,10 @@ separate project-level config file). `.DS_Store` and a broad set of
178
181
  build/cache directories (`node_modules`, `dist`, `build`, `.next`, `.turbo`,
179
182
  `.parcel-cache`, `.vite`, `.angular`, `.dart_tool`, `.venv`, …) are hidden by
180
183
  default. Pass `--scope-omit-dir <name>` (repeatable) to override the omit
181
- list on the command line for one session.
184
+ list on the command line for one session. `.devbox` and `.direnv` are always
185
+ excluded regardless of saved settings or CLI overrides: they hold thousands
186
+ of generated directories (Nix store link trees) that would stall the
187
+ worktree watcher.
182
188
 
183
189
  The viewer keeps its per-project state under `.code-viewer/` at the repository
184
190
  root:
@@ -216,6 +222,15 @@ Services whose names collide across subdirectories are kept distinct via
216
222
  `docker:<service>@<relDir>` ids (cwd-direct compose files keep the historical
217
223
  `docker:<service>` id for backward compatibility).
218
224
 
225
+ **Supabase CLI** (`supabase start`) local projects are also auto-discovered,
226
+ even though the Supabase CLI does not write a `docker-compose.yml` into the
227
+ project directory. Any `supabase/config.toml` found by the same recursive scan
228
+ is picked up (`project_id` and `[db] port` are read from it), and the running
229
+ Postgres container is resolved directly via `docker ps` (matched by container
230
+ name and the `com.supabase.cli.project` label) instead of `docker compose ps`.
231
+ Connection defaults to the Supabase CLI's documented local credentials
232
+ (`postgres`/`postgres`/`postgres`).
233
+
219
234
  **Redis** support: browse DB 0–15, SCAN keys, and view values per
220
235
  type (string/hash/list as dedicated panes, set/zset/stream as JSON views).
221
236
  Edit values, delete keys, and create new keys (string/hash/list/set/zset/stream)
@@ -1052,6 +1052,10 @@ function normalizeBlameRef(ref, base) {
1052
1052
  }
1053
1053
  return { base, ref: rawRef };
1054
1054
  }
1055
+ function withAlwaysWorktreeOmitDirNames(names) {
1056
+ const missing = ALWAYS_WORKTREE_OMIT_DIR_NAMES.filter((name) => !names.includes(name));
1057
+ return missing.length ? [...names, ...missing] : names;
1058
+ }
1055
1059
  function run(args, cwd) {
1056
1060
  return runSync(resolveGitArgs(args), cwd, {
1057
1061
  timeout: GIT_COMMAND_TIMEOUT_MS
@@ -2118,10 +2122,11 @@ function truncateToNHunks(diffText, n, maxLines = Number.POSITIVE_INFINITY) {
2118
2122
  lineTruncated
2119
2123
  };
2120
2124
  }
2121
- var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS = 20000, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
2125
+ var BLAME_ZERO_SHA = "0000000000000000000000000000000000000000", WORKTREE_RECURSIVE_DEPTH_LIMIT = 32, WORKTREE_RECURSIVE_ENTRY_LIMIT = 50000, DEFAULT_REF_COMMIT_LIMIT = 100, MAX_REF_COMMIT_LIMIT = 500, COMMIT_FORMAT = "%H%x00%s%x00%an%x00%aI", ALWAYS_WORKTREE_OMIT_DIR_NAMES, DEFAULT_WORKTREE_OMIT_DIR_NAMES, GIT_COMMAND_TIMEOUT_MS = 20000, HISTORY_FORMAT = "%H%x00%s%x00%an%x00%aI%x00%P%x00%b", MAX_HISTORY_LIMIT = 200;
2122
2126
  var init_git = __esm(() => {
2123
2127
  init_command_resolver();
2124
2128
  init_runtime();
2129
+ ALWAYS_WORKTREE_OMIT_DIR_NAMES = [".devbox", ".direnv"];
2125
2130
  DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
2126
2131
  "node_modules",
2127
2132
  "bower_components",
@@ -2142,8 +2147,7 @@ var init_git = __esm(() => {
2142
2147
  "out",
2143
2148
  "target",
2144
2149
  ".gradle",
2145
- ".devbox",
2146
- ".direnv",
2150
+ ...ALWAYS_WORKTREE_OMIT_DIR_NAMES,
2147
2151
  ".pnpm-store",
2148
2152
  ".turbo",
2149
2153
  ".parcel-cache",
@@ -9608,7 +9612,7 @@ var init_spawn_runner = () => {};
9608
9612
  // web-src/server/database/adapters/docker-utils.ts
9609
9613
  import { spawnSync as spawnSync2 } from "node:child_process";
9610
9614
  function isDockerComposeServiceUnavailableError(err) {
9611
- return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError;
9615
+ return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError || err instanceof SupabaseDbContainerUnavailableError;
9612
9616
  }
9613
9617
  function dockerCommand() {
9614
9618
  return commandForExternal("docker");
@@ -9625,10 +9629,15 @@ function throwIfCachedComposeDockerCommandUnavailable(cwd) {
9625
9629
  const failure = composePsCache.get(cwd)?.error || "";
9626
9630
  throwIfDockerCommandUnavailableResult({ code: 1, stderr: failure });
9627
9631
  }
9628
- function parseComposePsOutput(stdout) {
9632
+ function parseDockerJsonLines(stdout) {
9629
9633
  const output = stdout.trim();
9630
- const containers = output.startsWith("[") ? JSON.parse(output) : output.split(`
9634
+ if (!output)
9635
+ return [];
9636
+ return output.startsWith("[") ? JSON.parse(output) : output.split(`
9631
9637
  `).filter(Boolean).map((line) => JSON.parse(line));
9638
+ }
9639
+ function parseComposePsOutput(stdout) {
9640
+ const containers = parseDockerJsonLines(stdout);
9632
9641
  const byService = new Map;
9633
9642
  for (const container of containers) {
9634
9643
  if (container.Service && container.Name && container.State === "running") {
@@ -9647,15 +9656,14 @@ function cacheComposePsFailure(cwd, result, now) {
9647
9656
  error: stderr
9648
9657
  });
9649
9658
  }
9650
- function runComposePsAsync(cwd, signal) {
9651
- throwIfAborted(signal, "docker compose ps aborted");
9659
+ function runDockerCliAsync(args, opts) {
9660
+ throwIfAborted(opts.signal, opts.abortMessage);
9652
9661
  if (spawnSyncImpl !== spawnSync2) {
9653
- const command = dockerCommand();
9654
- const proc = spawnSyncImpl(command, ["compose", "ps", "--format", "json", "--status", "running"], {
9662
+ const proc = spawnSyncImpl(dockerCommand(), args, {
9655
9663
  encoding: "utf8",
9656
9664
  timeout: 5000,
9657
9665
  stdio: ["ignore", "pipe", "pipe"],
9658
- cwd
9666
+ ...opts.cwd ? { cwd: opts.cwd } : {}
9659
9667
  });
9660
9668
  return Promise.resolve({
9661
9669
  stdout: String(proc.stdout || ""),
@@ -9666,16 +9674,24 @@ ${proc.error.message}` : ""}`,
9666
9674
  }
9667
9675
  return spawnTextAsync({
9668
9676
  command: dockerCommand(),
9669
- args: ["compose", "ps", "--format", "json", "--status", "running"],
9670
- cwd,
9677
+ args,
9678
+ ...opts.cwd ? { cwd: opts.cwd } : {},
9671
9679
  timeoutMs: 5000,
9672
- signal,
9680
+ signal: opts.signal,
9673
9681
  killSignal: "SIGKILL",
9674
- abortMessage: "docker compose ps aborted",
9675
- timeoutMessage: "docker compose ps timed out",
9682
+ abortMessage: opts.abortMessage,
9683
+ timeoutMessage: opts.timeoutMessage,
9676
9684
  rejectOnError: false
9677
9685
  });
9678
9686
  }
9687
+ function runComposePsAsync(cwd, signal) {
9688
+ return runDockerCliAsync(["compose", "ps", "--format", "json", "--status", "running"], {
9689
+ cwd,
9690
+ signal,
9691
+ abortMessage: "docker compose ps aborted",
9692
+ timeoutMessage: "docker compose ps timed out"
9693
+ });
9694
+ }
9679
9695
  async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal) {
9680
9696
  throwIfAborted(signal, "docker compose ps aborted");
9681
9697
  const now = Date.now();
@@ -9773,7 +9789,128 @@ async function resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd,
9773
9789
  }
9774
9790
  return containerName;
9775
9791
  }
9776
- var COMPOSE_CONTAINER_NAME_POSITIVE_TTL_MS = 30000, COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS = 3000, COMPOSE_PS_FAILURE_TTL_MS = 15000, composePsCache, composePsPending, spawnSyncImpl, DockerComposeServiceUnavailableError, DockerCommandUnavailableError;
9792
+ function supabaseDbContainerName(projectId) {
9793
+ return `supabase_db_${projectId}`;
9794
+ }
9795
+ function parseDockerPsOutput(stdout) {
9796
+ return parseDockerJsonLines(stdout);
9797
+ }
9798
+ function runDockerPsAsync(args, signal) {
9799
+ return runDockerCliAsync(args, {
9800
+ signal,
9801
+ abortMessage: "docker ps aborted",
9802
+ timeoutMessage: "docker ps timed out"
9803
+ });
9804
+ }
9805
+ async function resolveRunningSupabaseDbContainerAsync(projectId, signal) {
9806
+ throwIfAborted(signal, "docker ps aborted");
9807
+ const containerName = supabaseDbContainerName(projectId);
9808
+ const now = Date.now();
9809
+ const cached = supabaseContainerCache.get(projectId);
9810
+ if (cached) {
9811
+ if (cached.containerName && cached.positiveExpiresAt > now) {
9812
+ return cached.containerName;
9813
+ }
9814
+ if (!cached.containerName && cached.negativeExpiresAt > now) {
9815
+ return null;
9816
+ }
9817
+ }
9818
+ let pending = supabaseContainerPending.get(projectId);
9819
+ if (!pending) {
9820
+ const controller = new AbortController;
9821
+ pending = {
9822
+ controller,
9823
+ refs: 0,
9824
+ done: false,
9825
+ promise: (async () => {
9826
+ const startedAt = Date.now();
9827
+ const cacheMiss = (negativeTtlMs) => {
9828
+ supabaseContainerCache.set(projectId, {
9829
+ containerName: null,
9830
+ positiveExpiresAt: startedAt,
9831
+ negativeExpiresAt: startedAt + negativeTtlMs
9832
+ });
9833
+ return null;
9834
+ };
9835
+ let proc;
9836
+ try {
9837
+ proc = await runDockerPsAsync([
9838
+ "ps",
9839
+ "--filter",
9840
+ `name=^/${containerName}$`,
9841
+ "--filter",
9842
+ `label=com.supabase.cli.project=${projectId}`,
9843
+ "--filter",
9844
+ "status=running",
9845
+ "--format",
9846
+ "json"
9847
+ ], controller.signal);
9848
+ } catch (err) {
9849
+ if (isAbortLikeError(err, controller.signal))
9850
+ throw err;
9851
+ return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
9852
+ }
9853
+ if (proc.code !== 0) {
9854
+ throwIfDockerCommandUnavailableResult(proc);
9855
+ return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
9856
+ }
9857
+ let containers;
9858
+ try {
9859
+ containers = parseDockerPsOutput(proc.stdout);
9860
+ } catch {
9861
+ return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
9862
+ }
9863
+ const match = containers.some((c) => (c.Names || "").replace(/^\//, "") === containerName);
9864
+ if (!match)
9865
+ return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
9866
+ supabaseContainerCache.set(projectId, {
9867
+ containerName,
9868
+ positiveExpiresAt: startedAt + SUPABASE_CONTAINER_POSITIVE_TTL_MS,
9869
+ negativeExpiresAt: startedAt
9870
+ });
9871
+ return containerName;
9872
+ })().finally(() => {
9873
+ if (supabaseContainerPending.get(projectId) === pending) {
9874
+ supabaseContainerPending.delete(projectId);
9875
+ }
9876
+ if (pending)
9877
+ pending.done = true;
9878
+ })
9879
+ };
9880
+ supabaseContainerPending.set(projectId, pending);
9881
+ }
9882
+ pending.refs++;
9883
+ let released = false;
9884
+ const release = () => {
9885
+ if (released)
9886
+ return;
9887
+ released = true;
9888
+ pending.refs--;
9889
+ if (pending.refs <= 0 && !pending.done) {
9890
+ pending.controller.abort();
9891
+ }
9892
+ };
9893
+ signal?.addEventListener("abort", release, { once: true });
9894
+ if (signal?.aborted) {
9895
+ signal.removeEventListener("abort", release);
9896
+ release();
9897
+ throwIfAborted(signal, "docker ps aborted");
9898
+ }
9899
+ try {
9900
+ return await pending.promise;
9901
+ } finally {
9902
+ signal?.removeEventListener("abort", release);
9903
+ release();
9904
+ }
9905
+ }
9906
+ async function resolveRunningSupabaseDbContainerOrThrowAsync(projectId, signal) {
9907
+ const containerName = await resolveRunningSupabaseDbContainerAsync(projectId, signal);
9908
+ if (!containerName) {
9909
+ throw new SupabaseDbContainerUnavailableError(projectId);
9910
+ }
9911
+ return containerName;
9912
+ }
9913
+ var COMPOSE_CONTAINER_NAME_POSITIVE_TTL_MS = 30000, COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS = 3000, COMPOSE_PS_FAILURE_TTL_MS = 15000, composePsCache, composePsPending, spawnSyncImpl, DockerComposeServiceUnavailableError, DockerCommandUnavailableError, SupabaseDbContainerUnavailableError, SUPABASE_CONTAINER_POSITIVE_TTL_MS = 15000, SUPABASE_CONTAINER_NEGATIVE_TTL_MS = 3000, supabaseContainerCache, supabaseContainerPending;
9777
9914
  var init_docker_utils = __esm(() => {
9778
9915
  init_command_resolver();
9779
9916
  init_spawn_runner();
@@ -9798,6 +9935,17 @@ var init_docker_utils = __esm(() => {
9798
9935
  this.name = "DockerCommandUnavailableError";
9799
9936
  }
9800
9937
  };
9938
+ SupabaseDbContainerUnavailableError = class SupabaseDbContainerUnavailableError extends Error {
9939
+ projectId;
9940
+ status = 503;
9941
+ constructor(projectId) {
9942
+ super(`Supabase local DB container for project "${projectId}" is not running. Start it with: supabase start`);
9943
+ this.name = "SupabaseDbContainerUnavailableError";
9944
+ this.projectId = projectId;
9945
+ }
9946
+ };
9947
+ supabaseContainerCache = new Map;
9948
+ supabaseContainerPending = new Map;
9801
9949
  });
9802
9950
 
9803
9951
  // web-src/server/database/adapters/sql-capture.ts
@@ -10670,13 +10818,9 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
10670
10818
  throwIfCachedComposeDockerCommandUnavailable(cwd);
10671
10819
  return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
10672
10820
  }
10673
- const config = {
10674
- kind,
10675
- containerName,
10676
- user,
10677
- password,
10678
- database
10679
- };
10821
+ return fetchPostgresSchemasViaContainerAsync({ kind, containerName, user, password, database }, cacheKey, now, signal);
10822
+ }
10823
+ async function fetchPostgresSchemasViaContainerAsync(config, cacheKey, now, signal) {
10680
10824
  try {
10681
10825
  const sql = `SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema') AND schema_name NOT LIKE 'pg_toast%' AND schema_name NOT LIKE 'pg_temp_%' AND schema_name NOT LIKE 'pg_toast_temp_%' AND has_schema_privilege(schema_name, 'USAGE') ORDER BY CASE WHEN schema_name = 'public' THEN 0 ELSE 1 END, schema_name`;
10682
10826
  const result = await execInContainerAsync(config, sql, 1e4, signal);
@@ -10695,6 +10839,38 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
10695
10839
  return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
10696
10840
  }
10697
10841
  }
10842
+ function supabaseSchemasCacheKey(projectId) {
10843
+ return `supabase\x00${projectId}`;
10844
+ }
10845
+ async function listSupabaseSchemasAsync(projectId, signal) {
10846
+ const cacheKey = supabaseSchemasCacheKey(projectId);
10847
+ const now = Date.now();
10848
+ const cached = dockerSchemasCache.get(cacheKey);
10849
+ if (cached && cached.expiresAt > now)
10850
+ return [...cached.value];
10851
+ const containerName = await resolveRunningSupabaseDbContainerAsync(projectId, signal);
10852
+ if (!containerName) {
10853
+ return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
10854
+ }
10855
+ return fetchPostgresSchemasViaContainerAsync({
10856
+ kind: "postgresql",
10857
+ containerName,
10858
+ user: SUPABASE_LOCAL_DB_USER,
10859
+ password: SUPABASE_LOCAL_DB_PASSWORD,
10860
+ database: SUPABASE_LOCAL_DB_NAME
10861
+ }, cacheKey, now, signal);
10862
+ }
10863
+ async function openSupabaseDockerAdapterAsync(projectId, schema, signal) {
10864
+ const containerName = await resolveRunningSupabaseDbContainerOrThrowAsync(projectId, signal);
10865
+ return createDockerAdapter({
10866
+ kind: "postgresql",
10867
+ containerName,
10868
+ user: SUPABASE_LOCAL_DB_USER,
10869
+ password: SUPABASE_LOCAL_DB_PASSWORD,
10870
+ database: SUPABASE_LOCAL_DB_NAME,
10871
+ ...schema ? { schema } : {}
10872
+ });
10873
+ }
10698
10874
  async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatabase, schema, signal) {
10699
10875
  const containerName = await resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd, signal);
10700
10876
  const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || (kind === "postgresql" ? "postgres" : "root");
@@ -10709,7 +10885,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
10709
10885
  ...kind === "postgresql" && schema ? { schema } : {}
10710
10886
  });
10711
10887
  }
10712
- var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES;
10888
+ var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
10713
10889
  var init_docker = __esm(() => {
10714
10890
  init_mutate();
10715
10891
  init_sql_snapshot();
@@ -13595,6 +13771,39 @@ async function pathExistsAsync(path) {
13595
13771
  return false;
13596
13772
  }
13597
13773
  }
13774
+ async function walkForMarkerFileAsync(dir, depth, omitSet, hasCapacity, visitDir, signal) {
13775
+ if (signal?.aborted || !hasCapacity())
13776
+ return;
13777
+ if (depth > MAX_SCAN_DEPTH)
13778
+ return;
13779
+ await visitDir(dir);
13780
+ if (signal?.aborted || !hasCapacity())
13781
+ return;
13782
+ let entries;
13783
+ try {
13784
+ entries = await readdir(dir);
13785
+ } catch {
13786
+ return;
13787
+ }
13788
+ for (const entry of entries) {
13789
+ if (signal?.aborted || !hasCapacity())
13790
+ return;
13791
+ if (omitSet.has(entry.toLowerCase()))
13792
+ continue;
13793
+ const full = join8(dir, entry);
13794
+ let entryStat;
13795
+ try {
13796
+ entryStat = await lstat(full);
13797
+ } catch {
13798
+ continue;
13799
+ }
13800
+ if (entryStat.isSymbolicLink())
13801
+ continue;
13802
+ if (entryStat.isDirectory()) {
13803
+ await walkForMarkerFileAsync(full, depth + 1, omitSet, hasCapacity, visitDir, signal);
13804
+ }
13805
+ }
13806
+ }
13598
13807
  async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
13599
13808
  const cacheKey = dockerDiscoveryCacheKey(cwd, omitDirNames);
13600
13809
  const now = Date.now();
@@ -13606,13 +13815,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
13606
13815
  const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
13607
13816
  omitSet.add(".git");
13608
13817
  omitSet.add("node_modules");
13609
- async function scan(dir, depth) {
13610
- if (signal?.aborted)
13611
- return;
13612
- if (results.length >= MAX_DOCKER_SERVICES)
13613
- return;
13614
- if (depth > MAX_SCAN_DEPTH)
13615
- return;
13818
+ await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_DOCKER_SERVICES, async (dir) => {
13616
13819
  for (const filename of COMPOSE_FILENAMES) {
13617
13820
  const filepath = join8(dir, filename);
13618
13821
  if (await pathExistsAsync(filepath)) {
@@ -13620,37 +13823,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
13620
13823
  break;
13621
13824
  }
13622
13825
  }
13623
- if (signal?.aborted)
13624
- return;
13625
- if (results.length >= MAX_DOCKER_SERVICES)
13626
- return;
13627
- let entries;
13628
- try {
13629
- entries = await readdir(dir);
13630
- } catch {
13631
- return;
13632
- }
13633
- for (const entry of entries) {
13634
- if (signal?.aborted)
13635
- return;
13636
- if (results.length >= MAX_DOCKER_SERVICES)
13637
- return;
13638
- if (omitSet.has(entry.toLowerCase()))
13639
- continue;
13640
- const full = join8(dir, entry);
13641
- let entryStat;
13642
- try {
13643
- entryStat = await lstat(full);
13644
- } catch {
13645
- continue;
13646
- }
13647
- if (entryStat.isSymbolicLink())
13648
- continue;
13649
- if (entryStat.isDirectory())
13650
- await scan(full, depth + 1);
13651
- }
13652
- }
13653
- await scan(cwd, 0);
13826
+ }, signal);
13654
13827
  if (signal?.aborted)
13655
13828
  return cloneDockerDiscoveryResult(results);
13656
13829
  if (results.length >= MAX_DOCKER_SERVICES) {
@@ -13745,7 +13918,120 @@ function isSafeDockerDatabaseName(value) {
13745
13918
  return false;
13746
13919
  return /^[A-Za-z0-9_$.-]+$/.test(value);
13747
13920
  }
13748
- var SQLITE_EXTENSIONS, SQLITE_MAGIC = "SQLite format 3\x00", MAX_SCAN_DEPTH = 3, MAX_ENTRIES = 50, DOCKER_DISCOVERY_TTL_MS = 5000, SQLITE_DISCOVERY_TTL_MS = 5000, sqliteDiscoveryCache, COMPOSE_FILENAMES, MAX_DOCKER_SERVICES = 30, dockerDiscoveryCache;
13921
+ function cloneSupabaseDiscoveryResult(result) {
13922
+ return result.map((entry) => ({ ...entry }));
13923
+ }
13924
+ function parseSupabaseConfigToml(content) {
13925
+ let section = null;
13926
+ let projectId = null;
13927
+ let dbPort = null;
13928
+ for (const rawLine of content.split(`
13929
+ `)) {
13930
+ const line = rawLine.trim();
13931
+ if (!line || line.startsWith("#"))
13932
+ continue;
13933
+ const sectionMatch = line.match(/^\[([^\]]+)\]$/);
13934
+ if (sectionMatch) {
13935
+ section = sectionMatch[1];
13936
+ continue;
13937
+ }
13938
+ const kvMatch = line.match(/^([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.+)$/);
13939
+ if (!kvMatch)
13940
+ continue;
13941
+ const value = stripScalarSyntax(kvMatch[2]);
13942
+ if (section === null && kvMatch[1] === "project_id") {
13943
+ projectId = value;
13944
+ } else if (section === "db" && kvMatch[1] === "port") {
13945
+ dbPort = value;
13946
+ }
13947
+ }
13948
+ if (!projectId || !isSafeDockerServiceName(projectId))
13949
+ return null;
13950
+ return {
13951
+ projectId,
13952
+ dbPort: dbPort && /^\d+$/.test(dbPort) ? dbPort : DEFAULT_SUPABASE_DB_PORT
13953
+ };
13954
+ }
13955
+ async function discoverSupabaseCliProjectsAsync(cwd, omitDirNames = [], signal) {
13956
+ const cacheKey = discoveryCacheKey(cwd, omitDirNames);
13957
+ const now = Date.now();
13958
+ const cached = supabaseDiscoveryCache.get(cacheKey);
13959
+ if (cached && cached.expiresAt > now) {
13960
+ return cloneSupabaseDiscoveryResult(cached.result);
13961
+ }
13962
+ const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
13963
+ omitSet.add(".git");
13964
+ omitSet.add("node_modules");
13965
+ const results = [];
13966
+ await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_SUPABASE_PROJECTS, async (dir) => {
13967
+ const configPath = join8(dir, "supabase", "config.toml");
13968
+ if (!await pathExistsAsync(configPath))
13969
+ return;
13970
+ try {
13971
+ const content = await readFile2(configPath, "utf-8");
13972
+ const parsed = parseSupabaseConfigToml(content);
13973
+ if (!parsed)
13974
+ return;
13975
+ const relDir = relative3(cwd, dir);
13976
+ const isRoot = relDir === "" || relDir === ".";
13977
+ const relDirSlash = relDir.replace(/\\/g, "/");
13978
+ const id = isRoot ? `supabase:${parsed.projectId}` : `supabase:${parsed.projectId}@${encodeURIComponent(relDirSlash)}`;
13979
+ const labelPath = isRoot ? "" : ` — ${relDirSlash}`;
13980
+ results.push({
13981
+ id,
13982
+ path: isRoot ? "supabase/config.toml" : `${relDirSlash}/supabase/config.toml`,
13983
+ name: `${parsed.projectId} (Supabase CLI, postgres@127.0.0.1:${parsed.dbPort}/postgres${labelPath})`,
13984
+ sizeBytes: 0,
13985
+ kind: "postgresql",
13986
+ projectId: parsed.projectId,
13987
+ relDirSlash,
13988
+ dbPort: parsed.dbPort
13989
+ });
13990
+ } catch {}
13991
+ }, signal);
13992
+ if (signal?.aborted)
13993
+ return cloneSupabaseDiscoveryResult(results);
13994
+ supabaseDiscoveryCache.set(cacheKey, {
13995
+ expiresAt: now + SUPABASE_DISCOVERY_TTL_MS,
13996
+ result: cloneSupabaseDiscoveryResult(results)
13997
+ });
13998
+ return cloneSupabaseDiscoveryResult(results);
13999
+ }
14000
+ function parseSupabaseDbId(dbId) {
14001
+ if (!dbId.startsWith("supabase:"))
14002
+ return null;
14003
+ const rest = dbId.slice("supabase:".length);
14004
+ if (!rest)
14005
+ return null;
14006
+ const atIdx = rest.indexOf("@");
14007
+ let projectId;
14008
+ let relDir = "";
14009
+ if (atIdx >= 0) {
14010
+ if (rest.indexOf("@", atIdx + 1) >= 0)
14011
+ return null;
14012
+ projectId = rest.slice(0, atIdx);
14013
+ try {
14014
+ relDir = decodeURIComponent(rest.slice(atIdx + 1));
14015
+ } catch {
14016
+ return null;
14017
+ }
14018
+ if (!isSafeDockerRelDir(relDir))
14019
+ return null;
14020
+ } else {
14021
+ projectId = rest;
14022
+ }
14023
+ if (!isSafeDockerServiceName(projectId))
14024
+ return null;
14025
+ return { projectId, relDir };
14026
+ }
14027
+ async function findSupabaseCliProjectByDbIdAsync(cwd, dbId, omitDirNames, signal) {
14028
+ const parsed = parseSupabaseDbId(dbId);
14029
+ if (!parsed)
14030
+ return null;
14031
+ const projects = await discoverSupabaseCliProjectsAsync(cwd, omitDirNames, signal);
14032
+ return projects.find((p) => p.projectId === parsed.projectId && p.relDirSlash === parsed.relDir) || null;
14033
+ }
14034
+ var SQLITE_EXTENSIONS, SQLITE_MAGIC = "SQLite format 3\x00", MAX_SCAN_DEPTH = 3, MAX_ENTRIES = 50, DOCKER_DISCOVERY_TTL_MS = 5000, SQLITE_DISCOVERY_TTL_MS = 5000, sqliteDiscoveryCache, COMPOSE_FILENAMES, MAX_DOCKER_SERVICES = 30, dockerDiscoveryCache, MAX_SUPABASE_PROJECTS = 30, SUPABASE_DISCOVERY_TTL_MS = 5000, DEFAULT_SUPABASE_DB_PORT = "54322", supabaseDiscoveryCache;
13749
14035
  var init_discovery = __esm(() => {
13750
14036
  SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
13751
14037
  sqliteDiscoveryCache = new Map;
@@ -13756,6 +14042,7 @@ var init_discovery = __esm(() => {
13756
14042
  "compose.yaml"
13757
14043
  ];
13758
14044
  dockerDiscoveryCache = new Map;
14045
+ supabaseDiscoveryCache = new Map;
13759
14046
  });
13760
14047
 
13761
14048
  // web-src/core/id.ts
@@ -16750,8 +17037,9 @@ function sanitizeCssSize(v) {
16750
17037
  return isValidCssSize(v) ? v : undefined;
16751
17038
  }
16752
17039
  function isToolInternalDbId(dbId) {
16753
- if (!dbId || dbId.startsWith("docker:"))
17040
+ if (!dbId || dbId.startsWith("docker:") || dbId.startsWith("supabase:")) {
16754
17041
  return false;
17042
+ }
16755
17043
  return dbId.split(/[\\/]+/).some((part) => part.toLowerCase() === ".code-viewer");
16756
17044
  }
16757
17045
  function sanitizeRedis(v) {
@@ -16930,6 +17218,11 @@ async function getAdapter(r, _cwd, signal) {
16930
17218
  const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
16931
17219
  return dockerAdapterCache.getOrOpenAsync(cacheKey, () => openDockerAdapterAsync(docker.serviceName, docker.kind, docker.env, docker.composeDir, docker.database, r.schema, signal));
16932
17220
  }
17221
+ if (r.supabase) {
17222
+ const projectId = r.supabase.projectId;
17223
+ const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
17224
+ return dockerAdapterCache.getOrOpenAsync(cacheKey, () => openSupabaseDockerAdapterAsync(projectId, r.schema, signal));
17225
+ }
16933
17226
  return getConnection(r.resolved);
16934
17227
  }
16935
17228
  function sanitizeFilename(name) {
@@ -16956,9 +17249,35 @@ async function resolvePostgresSchema(info, requestedSchema, signal) {
16956
17249
  return "public";
16957
17250
  return schemas[0] || "public";
16958
17251
  }
17252
+ async function resolveSupabaseSchema(info, requestedSchema, signal) {
17253
+ if (requestedSchema)
17254
+ return requestedSchema;
17255
+ const schemas = await listSupabaseSchemasAsync(info.projectId, signal);
17256
+ if (schemas.includes("public"))
17257
+ return "public";
17258
+ return schemas[0] || "public";
17259
+ }
16959
17260
  async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
16960
17261
  if (!dbParam)
16961
17262
  return textError("missing db parameter", 400);
17263
+ if (dbParam.startsWith("supabase:")) {
17264
+ const parsed = parseSupabaseDbId(dbParam);
17265
+ if (!parsed)
17266
+ return textError("invalid supabase db id", 400);
17267
+ const info = await findSupabaseCliProjectByDbIdAsync(cwd, dbParam, omitDirNames, signal);
17268
+ if (!info)
17269
+ return textError("supabase project not found", 404);
17270
+ const requestedSchema = normalizeSchemaParam(schemaParam);
17271
+ if (requestedSchema instanceof Response)
17272
+ return requestedSchema;
17273
+ const schema = await resolveSupabaseSchema(info, requestedSchema, signal);
17274
+ return {
17275
+ resolved: dbParam,
17276
+ dbId: dbParam,
17277
+ supabase: info,
17278
+ ...schema ? { schema } : {}
17279
+ };
17280
+ }
16962
17281
  if (dbParam.startsWith("docker:")) {
16963
17282
  const parsed = parseDockerDbId(dbParam);
16964
17283
  if (!parsed)
@@ -17045,17 +17364,22 @@ async function expandDockerServicesForFiles(dockerServices, listDockerDatabases,
17045
17364
  }
17046
17365
  async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_DB_FILE_DISCOVERY_DEPS) {
17047
17366
  ensureInit();
17048
- const [sqliteSettled, dockerSettled] = await Promise.allSettled([
17367
+ const [sqliteSettled, dockerSettled, supabaseSettled] = await Promise.allSettled([
17049
17368
  deps.discoverSqliteFiles(cwd, omitDirNames, signal),
17050
- deps.discoverDockerDatabases(cwd, omitDirNames, signal)
17369
+ deps.discoverDockerDatabases(cwd, omitDirNames, signal),
17370
+ deps.discoverSupabaseCliProjects(cwd, omitDirNames, signal)
17051
17371
  ]);
17052
17372
  if (sqliteSettled.status === "rejected") {
17053
17373
  throw sqliteSettled.reason;
17054
17374
  }
17375
+ if (supabaseSettled.status === "rejected") {
17376
+ throw supabaseSettled.reason;
17377
+ }
17055
17378
  if (dockerSettled.status === "rejected" && isAbortLikeError(dockerSettled.reason, signal)) {
17056
17379
  throw dockerSettled.reason;
17057
17380
  }
17058
17381
  const sqliteFiles = sqliteSettled.value;
17382
+ const supabaseProjects = supabaseSettled.value;
17059
17383
  const dockerServices = dockerSettled.status === "fulfilled" ? dockerSettled.value : [];
17060
17384
  const dockerErrors = [];
17061
17385
  if (dockerSettled.status === "rejected") {
@@ -17073,7 +17397,8 @@ async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_D
17073
17397
  sizeBytes: f.sizeBytes,
17074
17398
  kind: "sqlite"
17075
17399
  })),
17076
- ...dockerEntries.map(toFileInfo)
17400
+ ...dockerEntries.map(toFileInfo),
17401
+ ...supabaseProjects.map(toFileInfo)
17077
17402
  ],
17078
17403
  ...dockerTruncated ? { truncated: true } : {},
17079
17404
  ...dockerErrors.length > 0 ? { dockerError: dockerErrors.join("; ") } : {}
@@ -17087,6 +17412,17 @@ async function createDbSchemasResponse(cwd, dbParam, schemaParam, omitDirNames,
17087
17412
  const r = await resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal);
17088
17413
  if (r instanceof Response)
17089
17414
  return { ok: false, response: r };
17415
+ if (r.supabase) {
17416
+ const projectId = r.supabase.projectId;
17417
+ const { result: schemas2, executedSql: executedSql2 } = await captureSql(() => listSupabaseSchemasAsync(projectId, signal));
17418
+ const body2 = {
17419
+ dbId: r.dbId,
17420
+ schemas: schemas2.map((name) => ({ name })),
17421
+ selectedSchema: r.schema,
17422
+ executedSql: executedSql2
17423
+ };
17424
+ return { ok: true, value: body2 };
17425
+ }
17090
17426
  if (!r.docker || r.docker.kind !== "postgresql") {
17091
17427
  const body2 = { dbId: r.dbId, schemas: [] };
17092
17428
  return { ok: true, value: body2 };
@@ -18128,7 +18464,7 @@ async function handleClose(cwd, req, omitDirNames) {
18128
18464
  const r = await resolveDb(cwd, body.db, omitDirNames, undefined, req.signal);
18129
18465
  if (r instanceof Response)
18130
18466
  return r;
18131
- if (r.docker) {
18467
+ if (r.docker || r.supabase) {
18132
18468
  dockerAdapterCache.close(r.dbId);
18133
18469
  dockerAdapterCache.closePrefix(`${r.dbId}\x00`);
18134
18470
  } else {
@@ -18343,7 +18679,8 @@ var init_handle = __esm(() => {
18343
18679
  DEFAULT_DB_FILE_DISCOVERY_DEPS = {
18344
18680
  discoverSqliteFiles: discoverSqliteFilesAsync,
18345
18681
  discoverDockerDatabases: discoverDockerDatabasesAsync,
18346
- listDockerDatabases: listDockerDatabasesAsync
18682
+ listDockerDatabases: listDockerDatabasesAsync,
18683
+ discoverSupabaseCliProjects: discoverSupabaseCliProjectsAsync
18347
18684
  };
18348
18685
  searchJobs = new Map;
18349
18686
  snapshotJobs = new Map;
@@ -19081,6 +19418,9 @@ async function checkDiscovery(cwd, scopeOmitDirNames, signal) {
19081
19418
  };
19082
19419
  }
19083
19420
  async function defaultDatastoreProbe(file, cwd, signal) {
19421
+ if (file.id.startsWith("supabase:")) {
19422
+ return probeSupabaseSource(file, cwd, signal);
19423
+ }
19084
19424
  switch (file.kind) {
19085
19425
  case "sqlite":
19086
19426
  return probeSqliteSource(file, cwd, signal);
@@ -19131,6 +19471,23 @@ async function probeDockerSqlSource(file, cwd, signal) {
19131
19471
  } catch {}
19132
19472
  }
19133
19473
  }
19474
+ async function probeSupabaseSource(file, cwd, signal) {
19475
+ const parsed = parseSupabaseDbId(file.id);
19476
+ if (!parsed)
19477
+ throw new Error("invalid supabase db id");
19478
+ const info = await findSupabaseCliProjectByDbIdAsync(cwd, file.id, undefined, signal);
19479
+ if (!info)
19480
+ throw new Error("supabase project not found");
19481
+ const adapter = await openSupabaseDockerAdapterAsync(info.projectId, undefined, signal);
19482
+ try {
19483
+ signal.throwIfAborted();
19484
+ await adapter.getTablesAsync(signal);
19485
+ } finally {
19486
+ try {
19487
+ adapter.close();
19488
+ } catch {}
19489
+ }
19490
+ }
19134
19491
  async function probeRedisSource(file, cwd, signal) {
19135
19492
  const info = await findDockerServiceByDbIdAsync(cwd, file.id, "redis", undefined, signal);
19136
19493
  if (!info)
@@ -21730,7 +22087,7 @@ function validateMcpOptionalDbId(raw) {
21730
22087
  const parsed = validateMcpDbId(raw);
21731
22088
  if (parsed.ok !== true)
21732
22089
  return parsed;
21733
- if (!parsed.value.startsWith("docker:")) {
22090
+ if (!parsed.value.startsWith("docker:") && !parsed.value.startsWith("supabase:")) {
21734
22091
  const pathError = validateMcpPath(parsed.value);
21735
22092
  if (pathError)
21736
22093
  return { ok: false, error: "invalid database path" };
@@ -22164,7 +22521,7 @@ Examples:
22164
22521
  }
22165
22522
  warnIfLegacyConfigPresent();
22166
22523
  if (scopeOmitDirCliOverride) {
22167
- scopeOmitDirNames = scopeOmitDirCliOverride;
22524
+ scopeOmitDirNames = withAlwaysWorktreeOmitDirNames(scopeOmitDirCliOverride);
22168
22525
  }
22169
22526
  scopeWatchLimit = worktreeWatchDirectoryLimitFromEnv();
22170
22527
  }
@@ -22180,7 +22537,7 @@ function applyPersistedSettings(state) {
22180
22537
  const prevExclude = scopeExcludeNames;
22181
22538
  const prevWatchLimit = scopeWatchLimit;
22182
22539
  if (!scopeOmitDirCliOverride && Array.isArray(state.scopeOmitDirs) && state.scopeOmitDirs.length > 0) {
22183
- scopeOmitDirNames = state.scopeOmitDirs;
22540
+ scopeOmitDirNames = withAlwaysWorktreeOmitDirNames(state.scopeOmitDirs);
22184
22541
  } else if (!scopeOmitDirCliOverride) {
22185
22542
  scopeOmitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES;
22186
22543
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,9 @@ browser's Database > Search tab, so the human can review the same workflow.
39
39
  1. Discover the datastore ids the running server has detected. This is the
40
40
  AI-friendly equivalent of opening the browser's Database tab and reading
41
41
  the sidebar — it lists every SQLite file plus PostgreSQL / MySQL / Redis
42
- / Elasticsearch / S3 service that any nearby `docker-compose` exposes.
42
+ / Elasticsearch / S3 service that any nearby `docker-compose` exposes, as
43
+ well as any local Supabase CLI (`supabase start`) project found via a
44
+ `supabase/config.toml` (id form `supabase:<project_id>`).
43
45
  Use the printed `id` as `--db` on every other command. Credentials and
44
46
  internal config are stripped server-side.
45
47
 
package/web/app.js CHANGED
@@ -18724,6 +18724,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
18724
18724
  const service = rest.split(/[@:]/, 1)[0];
18725
18725
  return service || "Docker";
18726
18726
  }
18727
+ if (dbId.startsWith("supabase:")) {
18728
+ const rest = dbId.slice("supabase:".length);
18729
+ const projectId = rest.split(/[@]/, 1)[0];
18730
+ return projectId || "Supabase";
18731
+ }
18727
18732
  const normalized = dbId.replace(/\\/g, "/");
18728
18733
  const lastSlash = normalized.lastIndexOf("/");
18729
18734
  return lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
@@ -20016,7 +20021,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20016
20021
  const opt = document.createElement("option");
20017
20022
  opt.value = f2.id;
20018
20023
  const isDocker = f2.id.startsWith("docker:");
20019
- const label = isDocker ? `${f2.name} (Docker)` : `${f2.path} (${formatSize(f2.sizeBytes)})`;
20024
+ const isSupabase = f2.id.startsWith("supabase:");
20025
+ const label = isDocker ? `${f2.name} (Docker)` : isSupabase ? `${f2.name} (Supabase)` : `${f2.path} (${formatSize(f2.sizeBytes)})`;
20020
20026
  opt.textContent = label;
20021
20027
  optionsFragment.appendChild(opt);
20022
20028
  }
@@ -20206,7 +20212,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20206
20212
  if (!currentDbInfo)
20207
20213
  return labelFromDbId(initial.dbId);
20208
20214
  const suffix = currentSchema ? ` / ${currentSchema}` : "";
20209
- if (currentDbInfo.id.startsWith("docker:")) {
20215
+ if (currentDbInfo.id.startsWith("docker:") || currentDbInfo.id.startsWith("supabase:")) {
20210
20216
  const m = currentDbInfo.name.match(/^(\S+)/);
20211
20217
  return `${m ? m[1] : currentDbInfo.name}${suffix}`;
20212
20218
  }
@@ -24709,7 +24715,7 @@ code-viewer annotate add-db --db app.db --tab query \\
24709
24715
  ],
24710
24716
  [
24711
24717
  "PostgreSQL",
24712
- "Detected from compose files. Multiple databases per server, plus a schema selector for switching schemas without reopening. Same inline row edit / insert / delete as SQLite."
24718
+ "Detected from compose files. Multiple databases per server, plus a schema selector for switching schemas without reopening. Same inline row edit / insert / delete as SQLite. Local Supabase CLI (`supabase start`) projects are also auto-discovered from `supabase/config.toml`, without needing a docker-compose file."
24713
24719
  ],
24714
24720
  [
24715
24721
  "Redis",
@@ -25368,7 +25374,7 @@ code-viewer annotate add-db --db app.db --tab query \\
25368
25374
  ],
25369
25375
  [
25370
25376
  "PostgreSQL",
25371
- "compose ファイルから検出。同一サーバー上の複数データベースに対応し、スキーマ切替セレクターで再オープンせずスキーマを切り替えられます。SQLite と同じく行のインライン編集 / 追加 / 削除に対応。"
25377
+ "compose ファイルから検出。同一サーバー上の複数データベースに対応し、スキーマ切替セレクターで再オープンせずスキーマを切り替えられます。SQLite と同じく行のインライン編集 / 追加 / 削除に対応。ローカルの Supabase CLI (`supabase start`) プロジェクトも `supabase/config.toml` から自動検出され、docker-compose ファイルは不要です。"
25372
25378
  ],
25373
25379
  [
25374
25380
  "Redis",