@sous-io/sous 0.1.1 → 0.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.
- package/README.md +115 -35
- package/bin/run.js +10 -1
- package/docs/markdown/README.md +27 -0
- package/docs/markdown/_sidebar.md +18 -0
- package/docs/markdown/commands.md +308 -0
- package/docs/markdown/config-discovery.md +74 -0
- package/docs/markdown/config-inspection.md +69 -0
- package/docs/markdown/config-layers.md +92 -0
- package/docs/markdown/config-variables.md +79 -0
- package/docs/markdown/configuration.md +71 -0
- package/docs/markdown/design-principles.md +59 -0
- package/docs/markdown/repositories-authoring.md +408 -0
- package/docs/markdown/repositories-consuming.md +580 -0
- package/docs/markdown/repositories-file-formats.md +1084 -0
- package/docs/markdown/repositories-variables.md +387 -0
- package/docs/markdown/repositories.md +303 -0
- package/docs/markdown/skill-categories.md +58 -0
- package/package.json +72 -8
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/SKILL.tpl.md +20 -20
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/about-something.md +2 -2
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/do-something.md +1 -1
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/advanced-patterns.md +6 -6
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/commands.md +5 -5
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/frontmatter.md +3 -3
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/SKILL.tpl.md +40 -25
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +70 -0
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +75 -0
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/create-skill/SKILL.tpl.md +8 -9
- package/recipes/core/sous-skills/sous.recipe.yaml +45 -0
- package/sous.config.schema.json +337 -0
- package/src/base-command.ts +220 -67
- package/src/commands/build.ts +150 -73
- package/src/commands/clear.ts +23 -15
- package/src/commands/compile.ts +74 -16
- package/src/commands/config/get.ts +110 -0
- package/src/commands/config/show.ts +32 -0
- package/src/commands/config/validate.ts +53 -0
- package/src/commands/help.ts +46 -0
- package/src/commands/launch.ts +36 -14
- package/src/commands/lock/rebuild.ts +241 -0
- package/src/commands/lock/show.ts +115 -0
- package/src/commands/namespace/list.ts +117 -0
- package/src/commands/namespace/show.ts +110 -0
- package/src/commands/prune.ts +3 -11
- package/src/commands/recipe/list.ts +95 -0
- package/src/commands/recipe/show.ts +301 -0
- package/src/commands/repo/add.ts +145 -0
- package/src/commands/repo/gc.ts +172 -0
- package/src/commands/repo/init.ts +136 -0
- package/src/commands/repo/link.ts +500 -0
- package/src/commands/repo/list.ts +179 -0
- package/src/commands/repo/release.ts +619 -0
- package/src/commands/repo/remove.ts +193 -0
- package/src/commands/repo/search.ts +189 -0
- package/src/commands/repo/submit.ts +133 -0
- package/src/commands/repo/unlink.ts +147 -0
- package/src/commands/subscription/add.ts +285 -0
- package/src/commands/subscription/list.ts +129 -0
- package/src/commands/subscription/remove.ts +181 -0
- package/src/commands/vars/ask.ts +374 -0
- package/src/commands/vars/index.ts +79 -0
- package/src/commands/vars/list.ts +67 -0
- package/src/commands/vars/show.ts +77 -0
- package/src/config-command.ts +30 -0
- package/src/lib/build-service.ts +206 -54
- package/src/lib/config-discovery.ts +220 -27
- package/src/lib/config-inspect.ts +145 -0
- package/src/lib/config-kernel.mjs +377 -0
- package/src/lib/config-schema.ts +361 -0
- package/src/lib/env-file.ts +328 -0
- package/src/lib/env-local.ts +18 -1
- package/src/lib/errors.ts +32 -0
- package/src/lib/include-resolver.ts +108 -15
- package/src/lib/interactive.ts +165 -0
- package/src/lib/markdown-compiler.ts +118 -37
- package/src/lib/package-info.ts +25 -0
- package/src/lib/pid-service.ts +32 -21
- package/src/lib/refs/find.ts +589 -0
- package/src/lib/refs/index.ts +12 -0
- package/src/lib/refs/pick.ts +147 -0
- package/src/lib/refs/scopes.ts +61 -0
- package/src/lib/repos/catalog-display.ts +116 -0
- package/src/lib/repos/catalog-inputs.ts +160 -0
- package/src/lib/repos/catalog.ts +722 -0
- package/src/lib/repos/core-recipe.ts +105 -0
- package/src/lib/repos/defaults.ts +175 -0
- package/src/lib/repos/formats/common.ts +389 -0
- package/src/lib/repos/formats/index-file.ts +215 -0
- package/src/lib/repos/formats/links-map.ts +96 -0
- package/src/lib/repos/formats/lockfile.ts +167 -0
- package/src/lib/repos/formats/patterns.ts +57 -0
- package/src/lib/repos/formats/recipe-manifest.ts +395 -0
- package/src/lib/repos/formats/repo-manifest.ts +88 -0
- package/src/lib/repos/formats/store-entry.ts +84 -0
- package/src/lib/repos/freshness.ts +208 -0
- package/src/lib/repos/git-clone.ts +312 -0
- package/src/lib/repos/identity.ts +89 -0
- package/src/lib/repos/index.ts +58 -0
- package/src/lib/repos/links.ts +353 -0
- package/src/lib/repos/load-manifest.ts +236 -0
- package/src/lib/repos/lock-service.ts +453 -0
- package/src/lib/repos/locked-namespace-resolver.ts +90 -0
- package/src/lib/repos/locked-recipes.ts +254 -0
- package/src/lib/repos/managed-layer.ts +422 -0
- package/src/lib/repos/namespace-resolver.ts +370 -0
- package/src/lib/repos/providers/base.ts +206 -0
- package/src/lib/repos/providers/git.ts +233 -0
- package/src/lib/repos/providers/github.ts +294 -0
- package/src/lib/repos/providers/gitlab.ts +263 -0
- package/src/lib/repos/providers/http.ts +102 -0
- package/src/lib/repos/providers/index-cache.ts +382 -0
- package/src/lib/repos/providers/index.ts +106 -0
- package/src/lib/repos/providers/local.ts +391 -0
- package/src/lib/repos/providers/provider.ts +401 -0
- package/src/lib/repos/recipe-config-layers.ts +287 -0
- package/src/lib/repos/recipe-targets.ts +223 -0
- package/src/lib/repos/ref-search.ts +46 -0
- package/src/lib/repos/ref.ts +513 -0
- package/src/lib/repos/reference-report.ts +122 -0
- package/src/lib/repos/release/bump.ts +161 -0
- package/src/lib/repos/release/git-state.ts +305 -0
- package/src/lib/repos/release/index-builder.ts +635 -0
- package/src/lib/repos/release/index.ts +16 -0
- package/src/lib/repos/release/plan.ts +512 -0
- package/src/lib/repos/release/submit-service.ts +496 -0
- package/src/lib/repos/release/tags.ts +243 -0
- package/src/lib/repos/release/validate.ts +463 -0
- package/src/lib/repos/resolver.ts +789 -0
- package/src/lib/repos/scaffold/index.ts +238 -0
- package/src/lib/repos/scaffold/templates.ts +413 -0
- package/src/lib/repos/seed.ts +414 -0
- package/src/lib/repos/store/contract.ts +64 -0
- package/src/lib/repos/store/hash.ts +114 -0
- package/src/lib/repos/store/recipe-store.ts +599 -0
- package/src/lib/repos/store/settings.ts +58 -0
- package/src/lib/repos/subscription-service.ts +2678 -0
- package/src/lib/repos/trust.ts +447 -0
- package/src/lib/settings.ts +546 -189
- package/src/lib/sous-home.ts +104 -0
- package/src/lib/state.ts +52 -20
- package/src/lib/vars/ask.ts +1152 -0
- package/src/lib/vars/definition-source.ts +252 -0
- package/src/lib/vars/display.ts +233 -0
- package/src/lib/vars/index.ts +18 -0
- package/src/lib/vars/ladder.ts +282 -0
- package/src/lib/vars/mappings.ts +265 -0
- package/src/lib/vars/names.ts +94 -0
- package/src/lib/vars/preanswers.ts +395 -0
- package/src/lib/vars/question-plan.ts +218 -0
- package/src/lib/vars/report.ts +228 -0
- package/src/lib/vars/safe-regex.ts +235 -0
- package/src/lib/vars/validate.ts +312 -0
- package/src/lib/watch-loop.ts +148 -0
- package/src/templating/init-liquid-engine.ts +58 -16
- package/src/utils/choice-prompt.ts +143 -0
- package/src/utils/command-errors.ts +186 -0
- package/src/utils/command-help.ts +45 -0
- package/src/utils/confirm-prompt.ts +110 -0
- package/src/utils/flags.ts +153 -0
- package/src/utils/formatting.ts +540 -55
- package/src/utils/prompts.ts +35 -1
- package/src/utils/sous-directory.ts +245 -0
- package/src/utils/table.ts +603 -0
- package/src/utils/value-prompt.ts +119 -0
- package/shared-prompts/_partials/resume-task.md +0 -51
- package/shared-prompts/_partials/sub-agent-delegation.md +0 -32
- package/shared-prompts/_partials/update-task-file.md +0 -52
- package/shared-prompts/memories/automated-browser-tasks/INDEX.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md +0 -102
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/auth-failure-handling.mjs +0 -81
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/chained-workflow.mjs +0 -126
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/simple-fetch.mjs +0 -92
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/architecture.md +0 -61
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/auth-and-sessions.md +0 -65
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/ctx-api.md +0 -96
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/installation.md +0 -104
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/script-conventions.md +0 -243
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/chrome-state.mjs +0 -148
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.mjs +0 -383
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.spec.mjs +0 -267
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/eslint.config.mjs +0 -56
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/harness.mjs +0 -169
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/keyring.mjs +0 -59
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/logger.mjs +0 -25
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/params.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/run.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/settings.tpl.mjs +0 -1
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/utils.mjs +0 -185
- package/shared-prompts/skills/automated-browser-tasks/create-automated-browser-task/SKILL.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/running-automated-browser-tasks/SKILL.tpl.md +0 -59
- package/shared-prompts/skills/automated-browser-tasks/update-automated-browser-task/SKILL.tpl.md +0 -47
- package/shared-prompts/skills/control-flow/approve/SKILL.tpl.md +0 -26
- package/shared-prompts/skills/control-flow/opine/SKILL.tpl.md +0 -58
- package/shared-prompts/skills/control-flow/repeat/SKILL.tpl.md +0 -27
- package/shared-prompts/skills/control-flow/research/SKILL.tpl.md +0 -34
- package/shared-prompts/skills/sous-skills/about-sous/SKILL.tpl.md +0 -51
- package/shared-prompts/skills/task-files/about-task-files/SKILL.tpl.md +0 -122
- package/shared-prompts/skills/task-files/continue-task-in-new-branch/SKILL.tpl.md +0 -80
- package/shared-prompts/skills/task-files/go/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/resume-task/SKILL.tpl.md +0 -13
- package/shared-prompts/skills/task-files/start-task/SKILL.tpl.md +0 -93
- package/shared-prompts/skills/task-files/update/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/update-task-file/SKILL.tpl.md +0 -13
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/substitutions.md +0 -0
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/references/liquid-filters.md +0 -0
package/src/commands/clear.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Flags } from "@oclif/core";
|
|
2
1
|
import { confirm } from "@inquirer/prompts";
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import { BaseCommand } from "../base-command.js";
|
|
5
4
|
import { resolveStateFilePath } from "../lib/build-service.js";
|
|
6
|
-
import { StateService } from "../lib/state.js";
|
|
5
|
+
import { isProtectedPath, StateService } from "../lib/state.js";
|
|
6
|
+
import { protectedRepoPaths } from "../lib/repos/links.js";
|
|
7
|
+
import { confirmationFlag } from "../utils/flags.js";
|
|
7
8
|
import { displayError, footer, heading, log, showCommandVars } from "../utils/formatting.js";
|
|
8
9
|
|
|
9
10
|
export default class Clear extends BaseCommand {
|
|
@@ -12,41 +13,48 @@ export default class Clear extends BaseCommand {
|
|
|
12
13
|
static examples = [
|
|
13
14
|
"<%= config.bin %> clear",
|
|
14
15
|
"<%= config.bin %> clear --force",
|
|
16
|
+
"<%= config.bin %> clear -y",
|
|
15
17
|
];
|
|
16
18
|
|
|
17
19
|
static flags = {
|
|
18
20
|
...BaseCommand.baseFlags,
|
|
19
|
-
force
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}),
|
|
21
|
+
// `--force` is this command's original spelling, so it stays the primary
|
|
22
|
+
// one; `--yes` and `-y` are aliases of it, and the confirmation therefore
|
|
23
|
+
// answers the same way here as it does on every other command.
|
|
24
|
+
force: confirmationFlag({ primary: "force" }),
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
async run(): Promise<void> {
|
|
27
28
|
const { flags } = await this.parse(Clear);
|
|
28
29
|
|
|
29
|
-
const
|
|
30
|
-
const stateFilePath = resolveStateFilePath(project.key, this.settings, this.configContext);
|
|
30
|
+
const stateFilePath = resolveStateFilePath(this.settings, this.configContext);
|
|
31
31
|
|
|
32
32
|
const stateService = new StateService();
|
|
33
33
|
const state = await stateService.load(stateFilePath);
|
|
34
34
|
|
|
35
35
|
if (!state) {
|
|
36
36
|
displayError(
|
|
37
|
-
`No state file found at ${stateFilePath}. Run '
|
|
37
|
+
`No state file found at ${stateFilePath}. Run 'sous build' first, then 'sous clear' to recover.`
|
|
38
38
|
);
|
|
39
39
|
this.exit(1);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
showCommandVars({ Project:
|
|
42
|
+
showCommandVars({ Project: this.projectLabel, Config: this.configContext.configPath });
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
// A linked checkout holds somebody's unpushed edits and the recipe store is
|
|
45
|
+
// shared by every project on this machine, so neither is ever clearable,
|
|
46
|
+
// whatever a stale state entry claims.
|
|
47
|
+
const protectedPaths = protectedRepoPaths(this.configContext.sousDir);
|
|
48
|
+
const clearable = state!.files.filter(
|
|
49
|
+
(entry) => !isProtectedPath(entry.dest, protectedPaths)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const fileCount = clearable.length;
|
|
45
53
|
const dirCount = state!.dirs.length;
|
|
46
54
|
|
|
47
55
|
if (!flags.force) {
|
|
48
56
|
const confirmed = await confirm({
|
|
49
|
-
message: `Delete ${fileCount} file(s) and ${dirCount} director(ies) for
|
|
57
|
+
message: `Delete ${fileCount} file(s) and ${dirCount} director(ies) for '${this.projectLabel}'?`,
|
|
50
58
|
default: false,
|
|
51
59
|
});
|
|
52
60
|
if (!confirmed) {
|
|
@@ -57,8 +65,8 @@ export default class Clear extends BaseCommand {
|
|
|
57
65
|
|
|
58
66
|
heading("Clearing");
|
|
59
67
|
|
|
60
|
-
stateService.deleteTrackedFiles(
|
|
61
|
-
for (const entry of
|
|
68
|
+
stateService.deleteTrackedFiles(clearable, state!.dirs, protectedPaths);
|
|
69
|
+
for (const entry of clearable) log(` ✗ ${entry.dest}`);
|
|
62
70
|
|
|
63
71
|
// Delete state file itself
|
|
64
72
|
if (fs.existsSync(stateFilePath)) {
|
package/src/commands/compile.ts
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
2
|
import { BaseCommand } from "../base-command.js";
|
|
3
3
|
import { CompilationService } from "../lib/markdown-compiler.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { resolveCompilation, resolveRootScope } from "../lib/settings.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveRecipeTargets,
|
|
7
|
+
resolveStateFilePath,
|
|
8
|
+
withRecipeTargets,
|
|
9
|
+
} from "../lib/build-service.js";
|
|
10
|
+
import { createProjectNamespaceResolver } from "../lib/repos/locked-namespace-resolver.js";
|
|
11
|
+
import { describeLinkedRepos } from "../lib/repos/links.js";
|
|
12
|
+
import { buildReloadWatchConfig, startConfigReloadWatch } from "../lib/watch-loop.js";
|
|
6
13
|
import { WatchService } from "../lib/watch-service.js";
|
|
7
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
displayError,
|
|
16
|
+
footer,
|
|
17
|
+
heading,
|
|
18
|
+
log,
|
|
19
|
+
showCommandVars,
|
|
20
|
+
warning,
|
|
21
|
+
} from "../utils/formatting.js";
|
|
8
22
|
|
|
9
23
|
export default class Compile extends BaseCommand {
|
|
10
24
|
static description = "Compile markdown templates into output files";
|
|
11
25
|
|
|
12
26
|
static examples = [
|
|
13
27
|
"<%= config.bin %> compile",
|
|
14
|
-
"<%= config.bin %> compile --project myproject",
|
|
15
28
|
"<%= config.bin %> compile --rebuild",
|
|
16
29
|
"<%= config.bin %> compile --dry-run",
|
|
17
30
|
"<%= config.bin %> compile --strict",
|
|
@@ -41,19 +54,33 @@ export default class Compile extends BaseCommand {
|
|
|
41
54
|
async run(): Promise<void> {
|
|
42
55
|
const { flags } = await this.parse(Compile);
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
57
|
+
// The recipes this project subscribes to contribute compile targets
|
|
58
|
+
// alongside its own; both go through the same compiler.
|
|
59
|
+
const withRecipes = () => {
|
|
60
|
+
const scope = resolveRootScope(this.settings, this.configContext);
|
|
61
|
+
const recipes = resolveRecipeTargets(this.settings, scope, this.configContext);
|
|
62
|
+
for (const notice of recipes.warnings) warning(notice);
|
|
63
|
+
return withRecipeTargets(
|
|
64
|
+
resolveCompilation(this.settings, scope),
|
|
65
|
+
recipes,
|
|
66
|
+
this.settings,
|
|
67
|
+
scope
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
for (const line of describeLinkedRepos(this.configContext.sousDir)) log(line);
|
|
72
|
+
|
|
73
|
+
const config = withRecipes();
|
|
47
74
|
|
|
48
75
|
if (!config) {
|
|
49
|
-
displayError(`
|
|
76
|
+
displayError(`No compilation config found in ${this.configContext.configPath}`);
|
|
50
77
|
this.exit(1);
|
|
51
78
|
}
|
|
52
79
|
|
|
53
|
-
const stateFilePath = resolveStateFilePath(
|
|
80
|
+
const stateFilePath = resolveStateFilePath(this.settings, this.configContext);
|
|
54
81
|
|
|
55
82
|
showCommandVars({
|
|
56
|
-
Project:
|
|
83
|
+
Project: this.projectLabel,
|
|
57
84
|
Config: this.configContext.configPath,
|
|
58
85
|
Strict: flags.strict,
|
|
59
86
|
Rebuild: flags.rebuild,
|
|
@@ -62,13 +89,24 @@ export default class Compile extends BaseCommand {
|
|
|
62
89
|
|
|
63
90
|
heading("Compiling");
|
|
64
91
|
|
|
92
|
+
// Rebuilt on every compile rather than captured once: a watch-mode reload
|
|
93
|
+
// may follow a subscribe, and the lockfile is what this reads.
|
|
94
|
+
const namespaceResolver = () =>
|
|
95
|
+
createProjectNamespaceResolver({
|
|
96
|
+
sousDir: this.configContext.sousDir,
|
|
97
|
+
settings: this.settings,
|
|
98
|
+
});
|
|
99
|
+
|
|
65
100
|
const compilerOptions = {
|
|
66
101
|
strict: flags.strict,
|
|
67
102
|
rebuild: flags.rebuild,
|
|
68
103
|
dryRun: flags["dry-run"],
|
|
69
104
|
};
|
|
70
105
|
|
|
71
|
-
const compiler = new CompilationService(
|
|
106
|
+
const compiler = new CompilationService({
|
|
107
|
+
...compilerOptions,
|
|
108
|
+
namespaceResolver: namespaceResolver(),
|
|
109
|
+
});
|
|
72
110
|
const success = await compiler.compile(config!, stateFilePath);
|
|
73
111
|
|
|
74
112
|
footer();
|
|
@@ -78,15 +116,35 @@ export default class Compile extends BaseCommand {
|
|
|
78
116
|
}
|
|
79
117
|
|
|
80
118
|
if (flags.watch) {
|
|
81
|
-
const watchConfig = resolveWatchConfig(project, rootScope, project.key);
|
|
82
119
|
const watchService = new WatchService();
|
|
83
120
|
|
|
84
|
-
|
|
85
|
-
|
|
121
|
+
// Recompiles using the command's CURRENT settings. Called for partial
|
|
122
|
+
// rebuilds (a changed source) and, after a clean reload, for full
|
|
123
|
+
// rebuilds. The compilation config and state-file path are re-resolved
|
|
124
|
+
// each time so a config reload takes effect. Owns the "Recompiling"
|
|
125
|
+
// heading/footer. (`changedFile` is accepted for parity with build and
|
|
126
|
+
// logged by the watch loop; compile always does a full recompile.)
|
|
127
|
+
const rebuild = async (_changedFile?: string) => {
|
|
128
|
+
const currentConfig = withRecipes();
|
|
129
|
+
if (!currentConfig) {
|
|
130
|
+
displayError(`No compilation config found in ${this.configContext.configPath}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const currentStateFilePath = resolveStateFilePath(this.settings, this.configContext);
|
|
86
134
|
heading("Recompiling");
|
|
87
|
-
const recompiler = new CompilationService(
|
|
88
|
-
|
|
135
|
+
const recompiler = new CompilationService({
|
|
136
|
+
...compilerOptions,
|
|
137
|
+
namespaceResolver: namespaceResolver(),
|
|
138
|
+
});
|
|
139
|
+
await recompiler.compile(currentConfig, currentStateFilePath);
|
|
89
140
|
footer();
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
startConfigReloadWatch({
|
|
144
|
+
watchService,
|
|
145
|
+
buildWatchConfig: () => buildReloadWatchConfig(this.settings, this.configContext),
|
|
146
|
+
rebuild,
|
|
147
|
+
reloadConfig: () => this.reloadDiscoveredConfig(),
|
|
90
148
|
});
|
|
91
149
|
|
|
92
150
|
await new Promise(() => {}); // keep process alive
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { Args, Flags } from "@oclif/core";
|
|
3
|
+
import { ConfigCommand } from "../../config-command.js";
|
|
4
|
+
import { ConfigError, loadSettingsWithLayers } from "../../lib/settings.js";
|
|
5
|
+
import {
|
|
6
|
+
isScalar,
|
|
7
|
+
lookupPath,
|
|
8
|
+
NOT_FOUND,
|
|
9
|
+
parsePath,
|
|
10
|
+
renderJson,
|
|
11
|
+
truncateJson,
|
|
12
|
+
} from "../../lib/config-inspect.js";
|
|
13
|
+
|
|
14
|
+
export default class ConfigGet extends ConfigCommand {
|
|
15
|
+
static description =
|
|
16
|
+
"Print one value from the merged config by dot-path (e.g. compilation.targets[0].entryPoint)";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The other spelling of the topic. It lives under a hidden topic, so it is
|
|
20
|
+
* typable everywhere without ever reaching the top-level listing.
|
|
21
|
+
*/
|
|
22
|
+
static aliases = ["configs:get"];
|
|
23
|
+
|
|
24
|
+
static examples = [
|
|
25
|
+
"<%= config.bin %> config get name",
|
|
26
|
+
"<%= config.bin %> config get compilation.targets[0].entryPoint",
|
|
27
|
+
"<%= config.bin %> config get compilation --layers",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
static args = {
|
|
31
|
+
path: Args.string({
|
|
32
|
+
description: "Dot-path into the config, with [n] for array indices",
|
|
33
|
+
required: true,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
static flags = {
|
|
38
|
+
...ConfigCommand.baseFlags,
|
|
39
|
+
layers: Flags.boolean({
|
|
40
|
+
description: "Show which config layer set the value (per-layer provenance)",
|
|
41
|
+
default: false,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
async run(): Promise<void> {
|
|
46
|
+
const { args, flags } = await this.parse(ConfigGet);
|
|
47
|
+
const segments = parsePath(args.path);
|
|
48
|
+
|
|
49
|
+
if (flags.layers) {
|
|
50
|
+
await this.showLayers(args.path, segments);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const value = lookupPath(this.settings, segments);
|
|
55
|
+
if (value === NOT_FOUND) {
|
|
56
|
+
throw new ConfigError(
|
|
57
|
+
`No value at config path '${args.path}'.\n` +
|
|
58
|
+
` It is not present in the merged config (${this.configContext.configPath}).\n` +
|
|
59
|
+
` Run 'sous config show' to see the whole config.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Scalars print raw (no quotes) so the output is directly usable; objects and
|
|
64
|
+
// arrays print as pretty JSON, colorized only for a TTY.
|
|
65
|
+
if (isScalar(value)) {
|
|
66
|
+
process.stdout.write(`${String(value)}\n`);
|
|
67
|
+
} else {
|
|
68
|
+
process.stdout.write(`${renderJson(value, Boolean(process.stdout.isTTY))}\n`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Prints one line per config layer whose cumulative snapshot CHANGED the value
|
|
74
|
+
* at `pathStr`, showing old -> new (JSON-encoded, truncated). The first layer
|
|
75
|
+
* counts as a change from (unset).
|
|
76
|
+
*/
|
|
77
|
+
private async showLayers(pathStr: string, segments: (string | number)[]): Promise<void> {
|
|
78
|
+
const { layers } = await loadSettingsWithLayers(this.discovered, { trace: true });
|
|
79
|
+
const sousDir = this.configContext.sousDir;
|
|
80
|
+
|
|
81
|
+
let previous: unknown = NOT_FOUND;
|
|
82
|
+
let printedAny = false;
|
|
83
|
+
|
|
84
|
+
for (const layer of layers) {
|
|
85
|
+
const current = lookupPath(layer.config, segments);
|
|
86
|
+
// JSON round-trip so structurally-equal values compare equal.
|
|
87
|
+
const unchanged =
|
|
88
|
+
current !== NOT_FOUND
|
|
89
|
+
? previous !== NOT_FOUND && JSON.stringify(current) === JSON.stringify(previous)
|
|
90
|
+
: previous === NOT_FOUND;
|
|
91
|
+
if (unchanged) continue;
|
|
92
|
+
|
|
93
|
+
const rel = path.relative(sousDir, layer.path);
|
|
94
|
+
const label = rel && !rel.startsWith("..") ? rel : layer.path;
|
|
95
|
+
const oldStr = previous === NOT_FOUND ? "(unset)" : truncateJson(previous);
|
|
96
|
+
const newStr = current === NOT_FOUND ? "(unset)" : truncateJson(current);
|
|
97
|
+
process.stdout.write(`${label}: ${oldStr} -> ${newStr}\n`);
|
|
98
|
+
|
|
99
|
+
previous = current;
|
|
100
|
+
printedAny = true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!printedAny) {
|
|
104
|
+
throw new ConfigError(
|
|
105
|
+
`No value at config path '${pathStr}' in any config layer.\n` +
|
|
106
|
+
` Run 'sous config show' to see the whole merged config.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ConfigCommand } from "../../config-command.js";
|
|
2
|
+
import { renderJson } from "../../lib/config-inspect.js";
|
|
3
|
+
|
|
4
|
+
export default class ConfigShow extends ConfigCommand {
|
|
5
|
+
static description =
|
|
6
|
+
"Print the merged config (all conf.d layers merged, before variable resolution) as JSON";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The other spelling of the topic. It lives under a hidden topic, so it is
|
|
10
|
+
* typable everywhere without ever reaching the top-level listing.
|
|
11
|
+
*/
|
|
12
|
+
static aliases = ["configs:show"];
|
|
13
|
+
|
|
14
|
+
static examples = [
|
|
15
|
+
"<%= config.bin %> config show",
|
|
16
|
+
"<%= config.bin %> config show | jq .compilation",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
static flags = {
|
|
20
|
+
...ConfigCommand.baseFlags,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
async run(): Promise<void> {
|
|
24
|
+
await this.parse(ConfigShow);
|
|
25
|
+
|
|
26
|
+
// this.settings is the merged config as written: every conf.d layer has been
|
|
27
|
+
// deep-merged and the whole thing schema-validated, but no ${var} has been
|
|
28
|
+
// resolved yet. Colorize only for a TTY so piped output stays valid JSON.
|
|
29
|
+
const useColor = Boolean(process.stdout.isTTY);
|
|
30
|
+
process.stdout.write(`${renderJson(this.settings, useColor)}\n`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BaseCommand } from "../../base-command.js";
|
|
2
|
+
import {
|
|
3
|
+
resolveCompilation,
|
|
4
|
+
resolveRootScope,
|
|
5
|
+
resolveTools,
|
|
6
|
+
resolveWatchConfig,
|
|
7
|
+
} from "../../lib/settings.js";
|
|
8
|
+
import { footer, heading, showVariables } from "../../utils/formatting.js";
|
|
9
|
+
|
|
10
|
+
export default class ConfigValidate extends BaseCommand {
|
|
11
|
+
static description =
|
|
12
|
+
"Validate the merged config: schema, then full variable resolution (fixpoint + substitution)";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The other spelling of the topic. It lives under a hidden topic, so it is
|
|
16
|
+
* typable everywhere without ever reaching the top-level listing.
|
|
17
|
+
*/
|
|
18
|
+
static aliases = ["configs:validate"];
|
|
19
|
+
|
|
20
|
+
static examples = ["<%= config.bin %> config validate"];
|
|
21
|
+
|
|
22
|
+
static flags = {
|
|
23
|
+
...BaseCommand.baseFlags,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
async run(): Promise<void> {
|
|
27
|
+
await this.parse(ConfigValidate);
|
|
28
|
+
|
|
29
|
+
// Discovery, the config kernel, assertFlatConfig and zod validation have all
|
|
30
|
+
// already run in BaseCommand.init(); a failure there exits before we get here.
|
|
31
|
+
// Running the resolvers now surfaces the errors validation alone cannot:
|
|
32
|
+
// fixpoint cycles, undefined ${vars}, and unresolved substitutions. Any
|
|
33
|
+
// ConfigError they throw is rendered by BaseCommand.catch (non-zero exit).
|
|
34
|
+
const rootScope = resolveRootScope(this.settings, this.configContext);
|
|
35
|
+
resolveCompilation(this.settings, rootScope);
|
|
36
|
+
const tools = resolveTools(this.settings, rootScope);
|
|
37
|
+
resolveWatchConfig(this.settings, rootScope);
|
|
38
|
+
|
|
39
|
+
const layerCount = this.configContext.layerPaths?.length ?? 1;
|
|
40
|
+
const targetCount = this.settings.compilation?.targets.length ?? 0;
|
|
41
|
+
const toolNames = Object.keys(tools);
|
|
42
|
+
|
|
43
|
+
heading("Config is valid");
|
|
44
|
+
showVariables({
|
|
45
|
+
"Config File": this.configContext.configPath,
|
|
46
|
+
Layers: layerCount,
|
|
47
|
+
Targets: targetCount,
|
|
48
|
+
Tools: toolNames.length > 0 ? toolNames.join(", ") : "(none)",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
footer();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sous help`, `sous help <command>`, `sous help <topic>`, `sous help <topic> <command>`.
|
|
3
|
+
*
|
|
4
|
+
* The word form of `--help`, and deliberately the same screen: it hands the
|
|
5
|
+
* arguments straight to the help class oclif itself uses for `--help`, so there
|
|
6
|
+
* is exactly one help renderer and no second copy to fall out of date.
|
|
7
|
+
*
|
|
8
|
+
* This exists because `sous help build` is what people type, and without a
|
|
9
|
+
* command by that name oclif answers "command help not found", which reads like
|
|
10
|
+
* sous has no help at all. Adding it here rather than installing
|
|
11
|
+
* `@oclif/plugin-help` keeps the dependency list as it is.
|
|
12
|
+
*
|
|
13
|
+
* It does not extend `BaseCommand`: reading the help must work from anywhere,
|
|
14
|
+
* including a directory with no `.sous/` above it, and `BaseCommand` requires a
|
|
15
|
+
* discoverable config.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Args, Command, loadHelpClass } from "@oclif/core";
|
|
19
|
+
|
|
20
|
+
export default class Help extends Command {
|
|
21
|
+
static description = "Print the help for sous, or for one command or topic";
|
|
22
|
+
|
|
23
|
+
static examples = [
|
|
24
|
+
"<%= config.bin %> help",
|
|
25
|
+
"<%= config.bin %> help build",
|
|
26
|
+
"<%= config.bin %> help repo",
|
|
27
|
+
"<%= config.bin %> help repo add",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/** A command id is one or more words, so the argument list is open-ended. */
|
|
31
|
+
static strict = false;
|
|
32
|
+
|
|
33
|
+
static args = {
|
|
34
|
+
command: Args.string({
|
|
35
|
+
description: "The command or topic to describe, such as 'build' or 'repo add'",
|
|
36
|
+
required: false,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
async run(): Promise<void> {
|
|
41
|
+
const { argv } = await this.parse(Help);
|
|
42
|
+
const HelpClass = await loadHelpClass(this.config);
|
|
43
|
+
const help = new HelpClass(this.config, this.config.pjson.oclif.helpOptions);
|
|
44
|
+
await help.showHelp(argv as string[]);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/commands/launch.ts
CHANGED
|
@@ -3,17 +3,19 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { BaseCommand } from "../base-command.js";
|
|
5
5
|
import { BuildService } from "../lib/build-service.js";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveRootScope, resolveTools } from "../lib/settings.js";
|
|
7
7
|
import { displayError, footer, heading, showCommandVars } from "../utils/formatting.js";
|
|
8
8
|
|
|
9
9
|
export default class Launch extends BaseCommand {
|
|
10
|
-
static description =
|
|
10
|
+
static description =
|
|
11
|
+
"Build this project's outputs, then start a coding agent configured in its config";
|
|
11
12
|
|
|
12
13
|
static examples = [
|
|
13
14
|
"<%= config.bin %> launch claude",
|
|
14
15
|
"<%= config.bin %> launch codex --no-build",
|
|
15
16
|
"<%= config.bin %> launch claude --continuous",
|
|
16
|
-
"<%= config.bin %> launch
|
|
17
|
+
"<%= config.bin %> launch claude --resume",
|
|
18
|
+
"<%= config.bin %> launch claude -- -c",
|
|
17
19
|
];
|
|
18
20
|
|
|
19
21
|
static args = {
|
|
@@ -26,7 +28,7 @@ export default class Launch extends BaseCommand {
|
|
|
26
28
|
static flags = {
|
|
27
29
|
...BaseCommand.baseFlags,
|
|
28
30
|
"no-build": Flags.boolean({
|
|
29
|
-
description: "Skip
|
|
31
|
+
description: "Skip sous build before launching",
|
|
30
32
|
default: false,
|
|
31
33
|
}),
|
|
32
34
|
continuous: Flags.boolean({
|
|
@@ -35,32 +37,52 @@ export default class Launch extends BaseCommand {
|
|
|
35
37
|
}),
|
|
36
38
|
};
|
|
37
39
|
|
|
40
|
+
// Any argument launch does not recognize is forwarded to the tool, after the
|
|
41
|
+
// config-defined tool args. Flags that collide with sous's own (e.g. claude's
|
|
42
|
+
// -c / --continue vs sous's -c / --config) can be forced through with `--`:
|
|
43
|
+
// everything after it is forwarded verbatim.
|
|
44
|
+
//
|
|
45
|
+
// strict=false lets extra positionals through; "--"=false is what routes
|
|
46
|
+
// unknown flags into argv instead of a NonExistentFlagsError (oclif rejects
|
|
47
|
+
// them even in non-strict mode otherwise). Disabling oclif's `--` handling
|
|
48
|
+
// means it would keep parsing sous flags past the separator, so run() splits
|
|
49
|
+
// argv at the first `--` itself, before oclif ever sees the tail.
|
|
50
|
+
static strict = false;
|
|
51
|
+
static "--" = false;
|
|
52
|
+
|
|
38
53
|
async run(): Promise<void> {
|
|
39
|
-
const
|
|
54
|
+
const sepIndex = this.argv.indexOf("--");
|
|
55
|
+
const ownArgv = sepIndex === -1 ? this.argv : this.argv.slice(0, sepIndex);
|
|
56
|
+
const verbatimArgs = sepIndex === -1 ? [] : this.argv.slice(sepIndex + 1);
|
|
57
|
+
|
|
58
|
+
const { args, argv, flags } = await this.parse(Launch, ownArgv);
|
|
59
|
+
|
|
60
|
+
// argv holds every token oclif did not claim, in original order; the first
|
|
61
|
+
// is the tool name itself.
|
|
62
|
+
const passThroughArgs = [...argv.slice(1).map(String), ...verbatimArgs];
|
|
40
63
|
|
|
41
|
-
const project = this.resolveProject(flags.project);
|
|
42
64
|
const rootScope = resolveRootScope(this.settings, this.configContext);
|
|
43
|
-
const tools =
|
|
65
|
+
const tools = resolveTools(this.settings, rootScope);
|
|
44
66
|
const toolConfig = tools[args.tool];
|
|
45
67
|
|
|
46
68
|
if (!toolConfig) {
|
|
47
69
|
displayError(
|
|
48
|
-
`Tool '${args.tool}' not found in
|
|
70
|
+
`Tool '${args.tool}' not found in ${this.configContext.configPath}. ` +
|
|
49
71
|
`Available tools: ${Object.keys(tools).join(", ") || "(none)"}`
|
|
50
72
|
);
|
|
51
73
|
this.exit(1);
|
|
52
74
|
}
|
|
53
75
|
|
|
54
|
-
const
|
|
55
|
-
const projectRoot = projectScope.projectRoot ?? process.cwd();
|
|
76
|
+
const projectRoot = rootScope.projectRoot ?? process.cwd();
|
|
56
77
|
|
|
57
78
|
showCommandVars({
|
|
58
|
-
Project:
|
|
79
|
+
Project: this.projectLabel,
|
|
59
80
|
Config: this.configContext.configPath,
|
|
60
81
|
Tool: args.tool,
|
|
61
82
|
"Project Root": projectRoot,
|
|
62
83
|
"No Build": flags["no-build"],
|
|
63
84
|
Continuous: flags.continuous,
|
|
85
|
+
...(passThroughArgs.length > 0 && { "Tool Args": passThroughArgs.join(" ") }),
|
|
64
86
|
});
|
|
65
87
|
|
|
66
88
|
const buildService = new BuildService();
|
|
@@ -69,14 +91,14 @@ export default class Launch extends BaseCommand {
|
|
|
69
91
|
// Build step (unless --no-build)
|
|
70
92
|
if (!flags["no-build"]) {
|
|
71
93
|
heading("Building");
|
|
72
|
-
await buildService.build(
|
|
94
|
+
await buildService.build(this.settings, {
|
|
73
95
|
configContext: this.configContext,
|
|
74
96
|
});
|
|
75
97
|
footer();
|
|
76
98
|
}
|
|
77
99
|
|
|
78
|
-
//
|
|
79
|
-
const launchArgs = [...(toolConfig.args ?? [])];
|
|
100
|
+
// Config-defined args first, then pass-through, then promptFile content
|
|
101
|
+
const launchArgs = [...(toolConfig.args ?? []), ...passThroughArgs];
|
|
80
102
|
if (toolConfig.promptFile) {
|
|
81
103
|
if (!fs.existsSync(toolConfig.promptFile)) {
|
|
82
104
|
displayError(`promptFile not found: ${toolConfig.promptFile}`);
|