clawchef 0.1.2 → 0.1.3
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 +11 -4
- package/dist/api.js +1 -0
- package/dist/cli.js +2 -0
- package/dist/openclaw/command-provider.d.ts +1 -1
- package/dist/openclaw/command-provider.js +4 -1
- package/dist/openclaw/mock-provider.d.ts +1 -1
- package/dist/openclaw/mock-provider.js +1 -1
- package/dist/openclaw/provider.d.ts +1 -1
- package/dist/openclaw/remote-provider.d.ts +1 -1
- package/dist/openclaw/remote-provider.js +1 -1
- package/dist/orchestrator.js +4 -1
- package/dist/scaffold.js +11 -7
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/api.ts +1 -0
- package/src/cli.ts +2 -0
- package/src/openclaw/command-provider.ts +10 -1
- package/src/openclaw/mock-provider.ts +6 -1
- package/src/openclaw/provider.ts +6 -1
- package/src/openclaw/remote-provider.ts +6 -1
- package/src/orchestrator.ts +8 -1
- package/src/scaffold.ts +11 -7
- package/src/types.ts +1 -0
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Recipe-driven OpenClaw environment orchestrator.
|
|
|
19
19
|
- Materializes files into target workspaces.
|
|
20
20
|
- Installs skills.
|
|
21
21
|
- Supports plugin preinstall via `openclaw.plugins[]` and runtime `--plugin` flags.
|
|
22
|
+
- Supports preserving existing OpenClaw state via `--keep-openclaw-state` (skip factory reset).
|
|
22
23
|
- Configures channels with `openclaw channels add`.
|
|
23
24
|
- Supports interactive channel login at the end of execution (`channels[].login: true`) for channels that expose login.
|
|
24
25
|
- Supports remote HTTP orchestration via runtime flags (`--provider remote`) when OpenClaw is reachable via API.
|
|
@@ -78,6 +79,12 @@ clawchef cook recipes/sample.yaml -s
|
|
|
78
79
|
Warning: `-s/--silent` suppresses the factory-reset confirmation and auto-chooses force reinstall on version mismatch.
|
|
79
80
|
Use it only in CI/non-interactive flows where destructive reset behavior is expected.
|
|
80
81
|
|
|
82
|
+
Keep existing OpenClaw state (skip reset and keep current version on mismatch):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
clawchef cook recipes/sample.yaml --keep-openclaw-state
|
|
86
|
+
```
|
|
87
|
+
|
|
81
88
|
From-zero OpenClaw bootstrap (recommended):
|
|
82
89
|
|
|
83
90
|
```bash
|
|
@@ -142,9 +149,9 @@ clawchef scaffold ./my-recipe-project --name meetingbot
|
|
|
142
149
|
Scaffold output:
|
|
143
150
|
|
|
144
151
|
- `package.json` with `telegram-api-mock-server` in `devDependencies`
|
|
145
|
-
- `
|
|
146
|
-
- `
|
|
147
|
-
- `
|
|
152
|
+
- `recipe.yaml` with `telegram-mock` channel, plugin preinstall, and `workspaces[].assets`
|
|
153
|
+
- `assets/{AGENTS.md,IDENTITY.md,SOUL.md,TOOLS.md}`
|
|
154
|
+
- `assets/scripts/scheduling.mjs`
|
|
148
155
|
- `test/recipe-smoke.test.mjs`
|
|
149
156
|
|
|
150
157
|
By default scaffold only writes files; it does not run `npm install`.
|
|
@@ -188,7 +195,7 @@ Notes:
|
|
|
188
195
|
|
|
189
196
|
- `validate()` throws on invalid recipe.
|
|
190
197
|
- `cook()` throws on runtime/configuration errors.
|
|
191
|
-
- `scaffold()` creates `package.json`, `
|
|
198
|
+
- `scaffold()` creates `package.json`, `recipe.yaml`, `assets/`, and `test/`.
|
|
192
199
|
|
|
193
200
|
## Variable precedence
|
|
194
201
|
|
package/dist/api.js
CHANGED
|
@@ -15,6 +15,7 @@ function normalizeCookOptions(options) {
|
|
|
15
15
|
allowMissing: Boolean(options.allowMissing),
|
|
16
16
|
verbose: Boolean(options.verbose),
|
|
17
17
|
silent: options.silent ?? true,
|
|
18
|
+
keepOpenClawState: false,
|
|
18
19
|
provider: options.provider ?? "command",
|
|
19
20
|
remote: options.remote ?? {},
|
|
20
21
|
};
|
package/dist/cli.js
CHANGED
|
@@ -77,6 +77,7 @@ export function buildCli() {
|
|
|
77
77
|
.option("--allow-missing", "Allow unresolved template variables", false)
|
|
78
78
|
.option("--verbose", "Verbose logging", false)
|
|
79
79
|
.option("-s, --silent", "Skip reset confirmation prompt", false)
|
|
80
|
+
.option("--keep-openclaw-state", "Preserve existing OpenClaw state (skip factory reset)", false)
|
|
80
81
|
.option("--provider <provider>", "Execution provider: command | remote | mock")
|
|
81
82
|
.option("--plugin <npm-spec>", "Preinstall plugin package (repeatable)", (v, p) => p.concat([v]), [])
|
|
82
83
|
.option("--remote-base-url <url>", "Remote OpenClaw API base URL")
|
|
@@ -94,6 +95,7 @@ export function buildCli() {
|
|
|
94
95
|
allowMissing: Boolean(opts.allowMissing),
|
|
95
96
|
verbose: Boolean(opts.verbose),
|
|
96
97
|
silent: Boolean(opts.silent),
|
|
98
|
+
keepOpenClawState: Boolean(opts.keepOpenclawState),
|
|
97
99
|
provider,
|
|
98
100
|
remote: {
|
|
99
101
|
base_url: opts.remoteBaseUrl ?? readEnv("CLAWCHEF_REMOTE_BASE_URL"),
|
|
@@ -2,7 +2,7 @@ import type { AgentDef, ChannelDef, ConversationDef, OpenClawSection } from "../
|
|
|
2
2
|
import type { EnsureVersionResult, OpenClawProvider, ResolvedWorkspaceDef } from "./provider.js";
|
|
3
3
|
export declare class CommandOpenClawProvider implements OpenClawProvider {
|
|
4
4
|
private readonly stagedMessages;
|
|
5
|
-
ensureVersion(config: OpenClawSection, dryRun: boolean, silent: boolean): Promise<EnsureVersionResult>;
|
|
5
|
+
ensureVersion(config: OpenClawSection, dryRun: boolean, silent: boolean, keepOpenClawState: boolean): Promise<EnsureVersionResult>;
|
|
6
6
|
factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
7
7
|
installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void>;
|
|
8
8
|
startGateway(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
@@ -221,7 +221,7 @@ function bootstrapRuntimeEnv(bootstrap) {
|
|
|
221
221
|
}
|
|
222
222
|
export class CommandOpenClawProvider {
|
|
223
223
|
stagedMessages = new Map();
|
|
224
|
-
async ensureVersion(config, dryRun, silent) {
|
|
224
|
+
async ensureVersion(config, dryRun, silent, keepOpenClawState) {
|
|
225
225
|
const bin = config.bin ?? "openclaw";
|
|
226
226
|
const installPolicy = config.install ?? "auto";
|
|
227
227
|
const useCmd = commandFor(config, "use_version", { bin, version: config.version });
|
|
@@ -272,6 +272,9 @@ export class CommandOpenClawProvider {
|
|
|
272
272
|
if (installedThisRun) {
|
|
273
273
|
throw new ClawChefError(`OpenClaw version mismatch after install: current ${currentVersion}, expected ${config.version}`);
|
|
274
274
|
}
|
|
275
|
+
if (keepOpenClawState) {
|
|
276
|
+
return { installedThisRun: false };
|
|
277
|
+
}
|
|
275
278
|
const choice = await chooseVersionMismatchAction(currentVersion, config.version, silent);
|
|
276
279
|
if (choice === "ignore") {
|
|
277
280
|
return { installedThisRun: false };
|
|
@@ -2,7 +2,7 @@ import type { AgentDef, ChannelDef, ConversationDef, OpenClawSection } from "../
|
|
|
2
2
|
import type { EnsureVersionResult, OpenClawProvider, ResolvedWorkspaceDef } from "./provider.js";
|
|
3
3
|
export declare class MockOpenClawProvider implements OpenClawProvider {
|
|
4
4
|
private state;
|
|
5
|
-
ensureVersion(config: OpenClawSection, _dryRun: boolean, _silent: boolean): Promise<EnsureVersionResult>;
|
|
5
|
+
ensureVersion(config: OpenClawSection, _dryRun: boolean, _silent: boolean, _keepOpenClawState: boolean): Promise<EnsureVersionResult>;
|
|
6
6
|
installPlugin(_config: OpenClawSection, _pluginSpec: string, _dryRun: boolean): Promise<void>;
|
|
7
7
|
factoryReset(_config: OpenClawSection, _dryRun: boolean): Promise<void>;
|
|
8
8
|
startGateway(_config: OpenClawSection, _dryRun: boolean): Promise<void>;
|
|
@@ -7,7 +7,7 @@ export class MockOpenClawProvider {
|
|
|
7
7
|
skills: new Set(),
|
|
8
8
|
messages: new Map(),
|
|
9
9
|
};
|
|
10
|
-
async ensureVersion(config, _dryRun, _silent) {
|
|
10
|
+
async ensureVersion(config, _dryRun, _silent, _keepOpenClawState) {
|
|
11
11
|
const policy = config.install ?? "auto";
|
|
12
12
|
const installed = this.state.installedVersions.has(config.version);
|
|
13
13
|
let installedThisRun = false;
|
|
@@ -6,7 +6,7 @@ export interface EnsureVersionResult {
|
|
|
6
6
|
installedThisRun: boolean;
|
|
7
7
|
}
|
|
8
8
|
export interface OpenClawProvider {
|
|
9
|
-
ensureVersion(config: OpenClawSection, dryRun: boolean, silent: boolean): Promise<EnsureVersionResult>;
|
|
9
|
+
ensureVersion(config: OpenClawSection, dryRun: boolean, silent: boolean, keepOpenClawState: boolean): Promise<EnsureVersionResult>;
|
|
10
10
|
installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void>;
|
|
11
11
|
factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
12
12
|
startGateway(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
@@ -5,7 +5,7 @@ export declare class RemoteOpenClawProvider implements OpenClawProvider {
|
|
|
5
5
|
private readonly remoteConfig;
|
|
6
6
|
constructor(remoteConfig: Partial<OpenClawRemoteConfig>);
|
|
7
7
|
private perform;
|
|
8
|
-
ensureVersion(config: OpenClawSection, dryRun: boolean, _silent: boolean): Promise<EnsureVersionResult>;
|
|
8
|
+
ensureVersion(config: OpenClawSection, dryRun: boolean, _silent: boolean, _keepOpenClawState: boolean): Promise<EnsureVersionResult>;
|
|
9
9
|
installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void>;
|
|
10
10
|
factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
11
11
|
startGateway(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
@@ -94,7 +94,7 @@ export class RemoteOpenClawProvider {
|
|
|
94
94
|
clearTimeout(timeout);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
async ensureVersion(config, dryRun, _silent) {
|
|
97
|
+
async ensureVersion(config, dryRun, _silent, _keepOpenClawState) {
|
|
98
98
|
const result = await this.perform(config, "ensure_version", {
|
|
99
99
|
install: config.install,
|
|
100
100
|
}, dryRun);
|
package/dist/orchestrator.js
CHANGED
|
@@ -122,11 +122,14 @@ export async function runRecipe(recipe, recipeOrigin, options, logger) {
|
|
|
122
122
|
const remoteMode = options.provider === "remote";
|
|
123
123
|
const workspacePaths = new Map();
|
|
124
124
|
logger.info(`Running recipe: ${recipe.name}`);
|
|
125
|
-
const versionResult = await provider.ensureVersion(recipe.openclaw, options.dryRun, options.silent);
|
|
125
|
+
const versionResult = await provider.ensureVersion(recipe.openclaw, options.dryRun, options.silent, options.keepOpenClawState);
|
|
126
126
|
logger.info(`OpenClaw version ready: ${recipe.openclaw.version}`);
|
|
127
127
|
if (versionResult.installedThisRun) {
|
|
128
128
|
logger.info("OpenClaw was installed in this run; skipping factory reset");
|
|
129
129
|
}
|
|
130
|
+
else if (options.keepOpenClawState) {
|
|
131
|
+
logger.info("Keeping existing OpenClaw state; skipping factory reset");
|
|
132
|
+
}
|
|
130
133
|
else {
|
|
131
134
|
const confirmed = await confirmFactoryReset(options);
|
|
132
135
|
if (!confirmed) {
|
package/dist/scaffold.js
CHANGED
|
@@ -76,7 +76,7 @@ openclaw:
|
|
|
76
76
|
|
|
77
77
|
workspaces:
|
|
78
78
|
- name: "\${workspace_name}"
|
|
79
|
-
assets: "
|
|
79
|
+
assets: "./assets"
|
|
80
80
|
|
|
81
81
|
agents:
|
|
82
82
|
- workspace: "\${workspace_name}"
|
|
@@ -134,11 +134,17 @@ console.log("[scheduling] " + message);
|
|
|
134
134
|
function makeRecipeSmokeTest() {
|
|
135
135
|
return `import test from "node:test";
|
|
136
136
|
import assert from "node:assert/strict";
|
|
137
|
+
import path from "node:path";
|
|
138
|
+
import { fileURLToPath } from "node:url";
|
|
137
139
|
import { access } from "node:fs/promises";
|
|
138
140
|
|
|
141
|
+
const testDir = path.dirname(fileURLToPath(import.meta.url));
|
|
142
|
+
const projectRoot = path.resolve(testDir, "..");
|
|
143
|
+
|
|
139
144
|
test("recipe scaffold files exist", async () => {
|
|
140
|
-
await access("
|
|
141
|
-
await access("
|
|
145
|
+
await access(path.join(projectRoot, "recipe.yaml"));
|
|
146
|
+
await access(path.join(projectRoot, "assets", "AGENTS.md"));
|
|
147
|
+
await access(path.join(projectRoot, "package.json"));
|
|
142
148
|
assert.ok(true);
|
|
143
149
|
});
|
|
144
150
|
`;
|
|
@@ -149,16 +155,14 @@ export async function scaffoldProject(targetDirArg, options = {}) {
|
|
|
149
155
|
const defaultName = path.basename(targetDir);
|
|
150
156
|
const rawProjectName = options.projectName?.trim() || defaultName;
|
|
151
157
|
const projectName = normalizeProjectName(rawProjectName);
|
|
152
|
-
const
|
|
153
|
-
const assetsDir = path.join(srcDir, `${projectName}-assets`);
|
|
158
|
+
const assetsDir = path.join(targetDir, "assets");
|
|
154
159
|
const assetsScriptsDir = path.join(assetsDir, "scripts");
|
|
155
160
|
const testDir = path.join(targetDir, "test");
|
|
156
|
-
await mkdir(srcDir, { recursive: true });
|
|
157
161
|
await mkdir(assetsDir, { recursive: true });
|
|
158
162
|
await mkdir(assetsScriptsDir, { recursive: true });
|
|
159
163
|
await mkdir(testDir, { recursive: true });
|
|
160
164
|
await writeFile(path.join(targetDir, "package.json"), makePackageJson(projectName), "utf8");
|
|
161
|
-
await writeFile(path.join(
|
|
165
|
+
await writeFile(path.join(targetDir, "recipe.yaml"), makeRecipeYaml(projectName), "utf8");
|
|
162
166
|
await writeFile(path.join(assetsDir, "AGENTS.md"), makeAgentsDoc(projectName), "utf8");
|
|
163
167
|
await writeFile(path.join(assetsDir, "IDENTITY.md"), makeIdentityDoc(projectName), "utf8");
|
|
164
168
|
await writeFile(path.join(assetsDir, "SOUL.md"), makeSoulDoc(), "utf8");
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -30,6 +30,7 @@ function normalizeCookOptions(options: CookOptions): RunOptions {
|
|
|
30
30
|
allowMissing: Boolean(options.allowMissing),
|
|
31
31
|
verbose: Boolean(options.verbose),
|
|
32
32
|
silent: options.silent ?? true,
|
|
33
|
+
keepOpenClawState: false,
|
|
33
34
|
provider: options.provider ?? "command",
|
|
34
35
|
remote: options.remote ?? {},
|
|
35
36
|
};
|
package/src/cli.ts
CHANGED
|
@@ -87,6 +87,7 @@ export function buildCli(): Command {
|
|
|
87
87
|
.option("--allow-missing", "Allow unresolved template variables", false)
|
|
88
88
|
.option("--verbose", "Verbose logging", false)
|
|
89
89
|
.option("-s, --silent", "Skip reset confirmation prompt", false)
|
|
90
|
+
.option("--keep-openclaw-state", "Preserve existing OpenClaw state (skip factory reset)", false)
|
|
90
91
|
.option("--provider <provider>", "Execution provider: command | remote | mock")
|
|
91
92
|
.option("--plugin <npm-spec>", "Preinstall plugin package (repeatable)", (v, p: string[]) => p.concat([v]), [])
|
|
92
93
|
.option("--remote-base-url <url>", "Remote OpenClaw API base URL")
|
|
@@ -104,6 +105,7 @@ export function buildCli(): Command {
|
|
|
104
105
|
allowMissing: Boolean(opts.allowMissing),
|
|
105
106
|
verbose: Boolean(opts.verbose),
|
|
106
107
|
silent: Boolean(opts.silent),
|
|
108
|
+
keepOpenClawState: Boolean(opts.keepOpenclawState),
|
|
107
109
|
provider,
|
|
108
110
|
remote: {
|
|
109
111
|
base_url: opts.remoteBaseUrl ?? readEnv("CLAWCHEF_REMOTE_BASE_URL"),
|
|
@@ -273,7 +273,12 @@ function bootstrapRuntimeEnv(bootstrap: OpenClawBootstrap | undefined): Record<s
|
|
|
273
273
|
export class CommandOpenClawProvider implements OpenClawProvider {
|
|
274
274
|
private readonly stagedMessages = new Map<string, StagedMessage[]>();
|
|
275
275
|
|
|
276
|
-
async ensureVersion(
|
|
276
|
+
async ensureVersion(
|
|
277
|
+
config: OpenClawSection,
|
|
278
|
+
dryRun: boolean,
|
|
279
|
+
silent: boolean,
|
|
280
|
+
keepOpenClawState: boolean,
|
|
281
|
+
): Promise<EnsureVersionResult> {
|
|
277
282
|
const bin = config.bin ?? "openclaw";
|
|
278
283
|
const installPolicy = config.install ?? "auto";
|
|
279
284
|
const useCmd = commandFor(config, "use_version", { bin, version: config.version });
|
|
@@ -338,6 +343,10 @@ export class CommandOpenClawProvider implements OpenClawProvider {
|
|
|
338
343
|
);
|
|
339
344
|
}
|
|
340
345
|
|
|
346
|
+
if (keepOpenClawState) {
|
|
347
|
+
return { installedThisRun: false };
|
|
348
|
+
}
|
|
349
|
+
|
|
341
350
|
const choice = await chooseVersionMismatchAction(currentVersion, config.version, silent);
|
|
342
351
|
|
|
343
352
|
if (choice === "ignore") {
|
|
@@ -21,7 +21,12 @@ export class MockOpenClawProvider implements OpenClawProvider {
|
|
|
21
21
|
messages: new Map(),
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
async ensureVersion(
|
|
24
|
+
async ensureVersion(
|
|
25
|
+
config: OpenClawSection,
|
|
26
|
+
_dryRun: boolean,
|
|
27
|
+
_silent: boolean,
|
|
28
|
+
_keepOpenClawState: boolean,
|
|
29
|
+
): Promise<EnsureVersionResult> {
|
|
25
30
|
const policy = config.install ?? "auto";
|
|
26
31
|
const installed = this.state.installedVersions.has(config.version);
|
|
27
32
|
let installedThisRun = false;
|
package/src/openclaw/provider.ts
CHANGED
|
@@ -7,7 +7,12 @@ export interface EnsureVersionResult {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface OpenClawProvider {
|
|
10
|
-
ensureVersion(
|
|
10
|
+
ensureVersion(
|
|
11
|
+
config: OpenClawSection,
|
|
12
|
+
dryRun: boolean,
|
|
13
|
+
silent: boolean,
|
|
14
|
+
keepOpenClawState: boolean,
|
|
15
|
+
): Promise<EnsureVersionResult>;
|
|
11
16
|
installPlugin(config: OpenClawSection, pluginSpec: string, dryRun: boolean): Promise<void>;
|
|
12
17
|
factoryReset(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
13
18
|
startGateway(config: OpenClawSection, dryRun: boolean): Promise<void>;
|
|
@@ -141,7 +141,12 @@ export class RemoteOpenClawProvider implements OpenClawProvider {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
async ensureVersion(
|
|
144
|
+
async ensureVersion(
|
|
145
|
+
config: OpenClawSection,
|
|
146
|
+
dryRun: boolean,
|
|
147
|
+
_silent: boolean,
|
|
148
|
+
_keepOpenClawState: boolean,
|
|
149
|
+
): Promise<EnsureVersionResult> {
|
|
145
150
|
const result = await this.perform(
|
|
146
151
|
config,
|
|
147
152
|
"ensure_version",
|
package/src/orchestrator.ts
CHANGED
|
@@ -148,11 +148,18 @@ export async function runRecipe(
|
|
|
148
148
|
const workspacePaths = new Map<string, string>();
|
|
149
149
|
|
|
150
150
|
logger.info(`Running recipe: ${recipe.name}`);
|
|
151
|
-
const versionResult = await provider.ensureVersion(
|
|
151
|
+
const versionResult = await provider.ensureVersion(
|
|
152
|
+
recipe.openclaw,
|
|
153
|
+
options.dryRun,
|
|
154
|
+
options.silent,
|
|
155
|
+
options.keepOpenClawState,
|
|
156
|
+
);
|
|
152
157
|
logger.info(`OpenClaw version ready: ${recipe.openclaw.version}`);
|
|
153
158
|
|
|
154
159
|
if (versionResult.installedThisRun) {
|
|
155
160
|
logger.info("OpenClaw was installed in this run; skipping factory reset");
|
|
161
|
+
} else if (options.keepOpenClawState) {
|
|
162
|
+
logger.info("Keeping existing OpenClaw state; skipping factory reset");
|
|
156
163
|
} else {
|
|
157
164
|
const confirmed = await confirmFactoryReset(options);
|
|
158
165
|
if (!confirmed) {
|
package/src/scaffold.ts
CHANGED
|
@@ -89,7 +89,7 @@ openclaw:
|
|
|
89
89
|
|
|
90
90
|
workspaces:
|
|
91
91
|
- name: "\${workspace_name}"
|
|
92
|
-
assets: "
|
|
92
|
+
assets: "./assets"
|
|
93
93
|
|
|
94
94
|
agents:
|
|
95
95
|
- workspace: "\${workspace_name}"
|
|
@@ -153,11 +153,17 @@ console.log("[scheduling] " + message);
|
|
|
153
153
|
function makeRecipeSmokeTest(): string {
|
|
154
154
|
return `import test from "node:test";
|
|
155
155
|
import assert from "node:assert/strict";
|
|
156
|
+
import path from "node:path";
|
|
157
|
+
import { fileURLToPath } from "node:url";
|
|
156
158
|
import { access } from "node:fs/promises";
|
|
157
159
|
|
|
160
|
+
const testDir = path.dirname(fileURLToPath(import.meta.url));
|
|
161
|
+
const projectRoot = path.resolve(testDir, "..");
|
|
162
|
+
|
|
158
163
|
test("recipe scaffold files exist", async () => {
|
|
159
|
-
await access("
|
|
160
|
-
await access("
|
|
164
|
+
await access(path.join(projectRoot, "recipe.yaml"));
|
|
165
|
+
await access(path.join(projectRoot, "assets", "AGENTS.md"));
|
|
166
|
+
await access(path.join(projectRoot, "package.json"));
|
|
161
167
|
assert.ok(true);
|
|
162
168
|
});
|
|
163
169
|
`;
|
|
@@ -171,18 +177,16 @@ export async function scaffoldProject(targetDirArg?: string, options: ScaffoldOp
|
|
|
171
177
|
const rawProjectName = options.projectName?.trim() || defaultName;
|
|
172
178
|
const projectName = normalizeProjectName(rawProjectName);
|
|
173
179
|
|
|
174
|
-
const
|
|
175
|
-
const assetsDir = path.join(srcDir, `${projectName}-assets`);
|
|
180
|
+
const assetsDir = path.join(targetDir, "assets");
|
|
176
181
|
const assetsScriptsDir = path.join(assetsDir, "scripts");
|
|
177
182
|
const testDir = path.join(targetDir, "test");
|
|
178
183
|
|
|
179
|
-
await mkdir(srcDir, { recursive: true });
|
|
180
184
|
await mkdir(assetsDir, { recursive: true });
|
|
181
185
|
await mkdir(assetsScriptsDir, { recursive: true });
|
|
182
186
|
await mkdir(testDir, { recursive: true });
|
|
183
187
|
|
|
184
188
|
await writeFile(path.join(targetDir, "package.json"), makePackageJson(projectName), "utf8");
|
|
185
|
-
await writeFile(path.join(
|
|
189
|
+
await writeFile(path.join(targetDir, "recipe.yaml"), makeRecipeYaml(projectName), "utf8");
|
|
186
190
|
await writeFile(path.join(assetsDir, "AGENTS.md"), makeAgentsDoc(projectName), "utf8");
|
|
187
191
|
await writeFile(path.join(assetsDir, "IDENTITY.md"), makeIdentityDoc(projectName), "utf8");
|
|
188
192
|
await writeFile(path.join(assetsDir, "SOUL.md"), makeSoulDoc(), "utf8");
|