@sous-io/sous 0.2.36 → 0.2.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sous-io/sous",
3
- "version": "0.2.36",
3
+ "version": "0.2.37",
4
4
  "description": "Compiles AI coding agent configuration (CLAUDE.md, skills, memories) from LiquidJS templates",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -11,7 +11,7 @@ formatVersion: 1
11
11
 
12
12
  namespace: core
13
13
  name: sous-skills
14
- version: 0.2.36
14
+ version: 0.2.37
15
15
 
16
16
  description: >-
17
17
  The skills that teach an agent what sous is and how it works: which files sous
@@ -28,7 +28,14 @@ export type CommandResult = {
28
28
  export type CommandRunner = (
29
29
  command: string,
30
30
  args: string[],
31
- options: { cwd?: string }
31
+ options: {
32
+ cwd?: string;
33
+ /**
34
+ * Start the command through the shell. Only for a fixed command line: it
35
+ * is how Windows finds a `.cmd` shim such as npm's.
36
+ */
37
+ shell?: boolean;
38
+ }
32
39
  ) => Promise<CommandResult>;
33
40
 
34
41
  /**
@@ -38,13 +45,14 @@ export type CommandRunner = (
38
45
  *
39
46
  * @param command - The executable to run.
40
47
  * @param args - Its arguments, already split.
41
- * @param options - Optional working directory.
48
+ * @param options - Optional working directory, and whether to start it through the shell.
42
49
  */
43
50
  export const spawnCommand: CommandRunner = (command, args, options = {}) =>
44
51
  new Promise<CommandResult>((resolve) => {
45
52
  const child = spawn(command, args, {
46
53
  cwd: options.cwd,
47
54
  stdio: ["ignore", "pipe", "pipe"],
55
+ shell: options.shell === true,
48
56
  });
49
57
 
50
58
  let stdout = "";
@@ -83,6 +83,25 @@ export function localRepoPath(url: string): string | undefined {
83
83
  return path.isAbsolute(trimmed) ? path.normalize(trimmed) : undefined;
84
84
  }
85
85
 
86
+ /**
87
+ * A directory spelled as an identity's owner path: its segments joined with `/`.
88
+ * The identity becomes directories in the store and the index cache, split on
89
+ * `/`, so a Windows path would otherwise land there as one segment holding its
90
+ * drive colon and backslashes. A drive (`C:`) or UNC (`\\server\share`) root
91
+ * becomes plain segments. A POSIX path comes back unchanged.
92
+ *
93
+ * identityOwnerPath("/home/me"); // -> "/home/me"
94
+ * identityOwnerPath("C:\\Users\\me"); // -> "/C/Users/me" (on Windows)
95
+ *
96
+ * @param directory - An absolute, normalized directory.
97
+ */
98
+ function identityOwnerPath(directory: string): string {
99
+ const { root } = path.parse(directory);
100
+ const rootSegments = root.split(/[\\/]/).map((segment) => segment.replace(/:$/, ""));
101
+ const segments = [...rootSegments, ...directory.slice(root.length).split(path.sep)];
102
+ return "/" + segments.filter((segment) => segment.length > 0).join("/");
103
+ }
104
+
86
105
  /** A URL that names its scheme, such as `https://` or `ssh://`. */
87
106
  const SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i;
88
107
 
@@ -290,7 +309,7 @@ export class LocalProvider extends ProviderBase {
290
309
 
291
310
  return {
292
311
  host: "localhost",
293
- owner: path.dirname(directory),
312
+ owner: identityOwnerPath(path.dirname(directory)),
294
313
  name: path.basename(directory),
295
314
  httpsUrl: directory,
296
315
  sshUrl: pathToFileURL(directory).href,
@@ -361,7 +361,8 @@ async function globalRoot(manager: GlobalManager, run: CommandRunner): Promise<s
361
361
  const query = GLOBAL_ROOT_QUERIES[manager];
362
362
  let output: string;
363
363
  try {
364
- const result = await run(manager, query.args, {});
364
+ // On Windows the managers are `.cmd` shims, which only the shell finds.
365
+ const result = await run(manager, query.args, { shell: process.platform === "win32" });
365
366
  if (result.code !== 0) return undefined;
366
367
  output = result.stdout.trim().split(/\r?\n/).pop()?.trim() ?? "";
367
368
  } catch {
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { createRequire } from "node:module";
5
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { globSync } from "glob";
7
7
  import { inferGlobBase, type CompilationConfig, type CompilationTarget, type ResolvedRuntimeContext } from "./markdown-compiler.js";
8
8
  import { buildAliasMap, resolveAliasPrefix, type AliasMap } from "./include-resolver.js";
@@ -329,7 +329,9 @@ export async function loadSettingsWithLayers(
329
329
 
330
330
  const attempts: { label: string; args: string[] }[] = [
331
331
  { label: "node", args: [CONFIG_KERNEL_PATH] },
332
- { label: "tsx", args: ["--import", tsxPath, CONFIG_KERNEL_PATH] },
332
+ // `--import` takes a module specifier, so the path goes as a file:// URL:
333
+ // a bare Windows path reads as a URL whose scheme is the drive letter.
334
+ { label: "tsx", args: ["--import", pathToFileURL(tsxPath).href, CONFIG_KERNEL_PATH] },
333
335
  ];
334
336
 
335
337
  const failures: { label: string; message: string }[] = [];