@openagentpack/cli 0.4.0 → 0.7.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 +9 -4
- package/dist/bin/agents.js +1 -1
- package/dist/{chunk-V2QDO63Z.js → chunk-C7XMIB3W.js} +476 -143
- package/dist/src/program.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -13,12 +13,17 @@ The package installs the `agents` command.
|
|
|
13
13
|
## Quick start
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
agents init
|
|
17
|
-
agents validate
|
|
18
|
-
agents
|
|
19
|
-
agents
|
|
16
|
+
agents project init
|
|
17
|
+
agents project validate
|
|
18
|
+
agents project build --dry-run
|
|
19
|
+
agents project build --yes
|
|
20
|
+
agents project publish --yes
|
|
20
21
|
```
|
|
21
22
|
|
|
23
|
+
Directory projects store Agents and Skills as JSON, Markdown, and local files. `project build` previews full directory changes against the current version HEAD, organizes shared Skills, and writes `.openagentpack/build/agents.yaml`; `project publish` consumes only a current Build and records a full source-tree snapshot after complete remote success. Workbench and CLI use the same Git-independent switch under `agents project version ...`; snapshots include local Skill and binary content but exclude `.openagentpack/state.json`.
|
|
24
|
+
|
|
25
|
+
The legacy `agents init|validate|plan|apply` YAML workflow and `agents playground -f agents.yaml` Session Preview remain available. YAML Apply is independent from directory versions.
|
|
26
|
+
|
|
22
27
|
Use `agents <command> --help` for command-specific options.
|
|
23
28
|
|
|
24
29
|
## Documentation
|
package/dist/bin/agents.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Command as Command2, Option as Option2 } from "commander";
|
|
|
7
7
|
// src/commands/apply.ts
|
|
8
8
|
import * as p2 from "@clack/prompts";
|
|
9
9
|
import { decideDestructive, executePlannedProject, UserError as UserError2 } from "@openagentpack/sdk";
|
|
10
|
+
import { assertLegacyYamlNotShadowed } from "@openagentpack/sdk/project-workspace";
|
|
10
11
|
import chalk3 from "chalk";
|
|
11
12
|
|
|
12
13
|
// src/config-loader.ts
|
|
@@ -273,6 +274,7 @@ async function confirmDrift(actions, file) {
|
|
|
273
274
|
return !p2.isCancel(shouldApply) && shouldApply;
|
|
274
275
|
}
|
|
275
276
|
async function applyCommand(options) {
|
|
277
|
+
await assertLegacyYamlNotShadowed(options.file);
|
|
276
278
|
const ctx = await buildCliRuntime(options.file);
|
|
277
279
|
assertProviderConfigured(ctx, options.provider);
|
|
278
280
|
const planned = await planProjectWithRefresh(ctx, {
|
|
@@ -360,12 +362,13 @@ ${chalk3.green(`${creates.length} to create`)}, ${chalk3.yellow(`${updates.lengt
|
|
|
360
362
|
const failed = result.results.filter((r) => r.status === "failed").length;
|
|
361
363
|
const skipped = result.results.filter((r) => r.status === "skipped").length;
|
|
362
364
|
s.stop("Apply finished.");
|
|
363
|
-
if (failed > 0) {
|
|
364
|
-
p2.log.warning(`${succeeded} succeeded, ${failed} failed, ${skipped} skipped.`, {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
if (failed > 0 || skipped > 0) {
|
|
366
|
+
p2.log.warning(`${succeeded} succeeded, ${failed} failed, ${skipped} skipped.`, {
|
|
367
|
+
output: process.stderr
|
|
368
|
+
});
|
|
369
|
+
throw new UserError2(failed > 0 ? "Apply failed." : "Apply incomplete: one or more actions were skipped.");
|
|
368
370
|
}
|
|
371
|
+
p2.log.success(`Apply complete! ${succeeded} actions executed successfully.`, { output: process.stderr });
|
|
369
372
|
}
|
|
370
373
|
|
|
371
374
|
// src/commands/deployment.ts
|
|
@@ -520,8 +523,10 @@ import {
|
|
|
520
523
|
destroyPlannedProjectResources,
|
|
521
524
|
planDestroyProjectContext
|
|
522
525
|
} from "@openagentpack/sdk";
|
|
526
|
+
import { assertLegacyYamlNotShadowed as assertLegacyYamlNotShadowed2 } from "@openagentpack/sdk/project-workspace";
|
|
523
527
|
import chalk6 from "chalk";
|
|
524
528
|
async function destroyCommand(options) {
|
|
529
|
+
await assertLegacyYamlNotShadowed2(options.file);
|
|
525
530
|
const ctx = await buildCliRuntime(options.file);
|
|
526
531
|
const planned = planDestroyProjectContext(ctx);
|
|
527
532
|
const resources = planned.resources;
|
|
@@ -630,6 +635,7 @@ function stopResourceSpinner(spinner4, result) {
|
|
|
630
635
|
// src/commands/init.ts
|
|
631
636
|
import { readFile, writeFile } from "fs/promises";
|
|
632
637
|
import * as p4 from "@clack/prompts";
|
|
638
|
+
import { UserError as UserError4 } from "@openagentpack/sdk";
|
|
633
639
|
|
|
634
640
|
// src/utils/file-utils.ts
|
|
635
641
|
import { existsSync } from "fs";
|
|
@@ -647,12 +653,10 @@ async function fileExists(path) {
|
|
|
647
653
|
}
|
|
648
654
|
|
|
649
655
|
// src/commands/init.ts
|
|
650
|
-
var
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
.
|
|
654
|
-
`;
|
|
655
|
-
function buildTemplate(opts) {
|
|
656
|
+
var PROVIDERS = ["bailian", "claude", "qoder", "ark", "all"];
|
|
657
|
+
var LOCAL_GITIGNORE_PATTERNS = ["agents.state.json", ".openagentpack/versions/", ".env"];
|
|
658
|
+
function buildTemplate(options) {
|
|
659
|
+
const agentKey = yamlMappingKey(options.agentName);
|
|
656
660
|
const providers = {
|
|
657
661
|
bailian: ` bailian:
|
|
658
662
|
api_key: \${DASHSCOPE_API_KEY}
|
|
@@ -665,34 +669,29 @@ function buildTemplate(opts) {
|
|
|
665
669
|
ark: ` ark:
|
|
666
670
|
api_key: \${ARK_API_KEY}`
|
|
667
671
|
};
|
|
668
|
-
|
|
669
|
-
if (opts.provider === "all") {
|
|
670
|
-
providerBlock = `${providers.bailian}
|
|
672
|
+
const providerBlock = options.provider === "all" ? `${providers.bailian}
|
|
671
673
|
${providers.claude}
|
|
672
674
|
${providers.qoder}
|
|
673
|
-
${providers.ark}
|
|
674
|
-
} else {
|
|
675
|
-
providerBlock = providers[opts.provider];
|
|
676
|
-
}
|
|
675
|
+
${providers.ark}` : providers[options.provider];
|
|
677
676
|
const singleModel = {
|
|
678
|
-
bailian:
|
|
679
|
-
claude:
|
|
680
|
-
qoder:
|
|
681
|
-
ark:
|
|
677
|
+
bailian: " model: qwen3.7-max",
|
|
678
|
+
claude: " model: claude-sonnet-4-6",
|
|
679
|
+
qoder: " model: ultimate",
|
|
680
|
+
ark: " model: doubao-seed-2-1-pro-260628"
|
|
682
681
|
};
|
|
683
|
-
const modelBlock =
|
|
682
|
+
const modelBlock = options.provider === "all" ? ` model:
|
|
684
683
|
bailian: qwen3.7-max
|
|
685
684
|
claude: claude-sonnet-4-6
|
|
686
685
|
qoder: ultimate
|
|
687
|
-
ark: doubao-seed-2-1-pro-260628` : singleModel[
|
|
688
|
-
const toolBlock =
|
|
686
|
+
ark: doubao-seed-2-1-pro-260628` : singleModel[options.provider];
|
|
687
|
+
const toolBlock = options.provider === "bailian" ? "[bash, read, glob, grep]" : "[read, glob, grep, web_search, web_fetch]";
|
|
689
688
|
return `version: "1"
|
|
690
689
|
|
|
691
690
|
providers:
|
|
692
691
|
${providerBlock}
|
|
693
692
|
|
|
694
693
|
defaults:
|
|
695
|
-
provider: ${
|
|
694
|
+
provider: ${options.provider === "all" ? "all" : options.provider}
|
|
696
695
|
|
|
697
696
|
environments:
|
|
698
697
|
dev:
|
|
@@ -702,7 +701,7 @@ environments:
|
|
|
702
701
|
type: unrestricted
|
|
703
702
|
|
|
704
703
|
agents:
|
|
705
|
-
${
|
|
704
|
+
${agentKey}:
|
|
706
705
|
description: "General-purpose assistant"
|
|
707
706
|
${modelBlock}
|
|
708
707
|
instructions: |
|
|
@@ -712,61 +711,67 @@ ${modelBlock}
|
|
|
712
711
|
builtin: ${toolBlock}
|
|
713
712
|
`;
|
|
714
713
|
}
|
|
715
|
-
|
|
714
|
+
function yamlMappingKey(value) {
|
|
715
|
+
return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(value) ? value : JSON.stringify(value);
|
|
716
|
+
}
|
|
717
|
+
function parseProvider(provider) {
|
|
718
|
+
if (PROVIDERS.includes(provider)) return provider;
|
|
719
|
+
throw new UserError4(`Unsupported provider '${provider}'. Choose one of: ${PROVIDERS.join(", ")}.`);
|
|
720
|
+
}
|
|
721
|
+
async function promptForProjectOptions(options) {
|
|
722
|
+
const provider = options.provider ? parseProvider(options.provider) : await p4.select({
|
|
723
|
+
message: "Which provider(s) do you want to use?",
|
|
724
|
+
options: [
|
|
725
|
+
{ value: "bailian", label: "Bailian (\u963F\u91CC\u4E91\u767E\u70BC)" },
|
|
726
|
+
{ value: "claude", label: "Claude" },
|
|
727
|
+
{ value: "qoder", label: "Qoder" },
|
|
728
|
+
{ value: "ark", label: "Ark\uFF08\u706B\u5C71\u65B9\u821F\uFF09" },
|
|
729
|
+
{ value: "all", label: "All providers" }
|
|
730
|
+
],
|
|
731
|
+
output: process.stderr
|
|
732
|
+
});
|
|
733
|
+
if (p4.isCancel(provider)) {
|
|
734
|
+
p4.cancel("Init cancelled.", { output: process.stderr });
|
|
735
|
+
process.exit(0);
|
|
736
|
+
}
|
|
737
|
+
const agentName = options.agentName ? options.agentName : await p4.text({
|
|
738
|
+
message: "Name your first agent:",
|
|
739
|
+
placeholder: "assistant",
|
|
740
|
+
defaultValue: "assistant",
|
|
741
|
+
output: process.stderr
|
|
742
|
+
});
|
|
743
|
+
if (p4.isCancel(agentName)) {
|
|
744
|
+
p4.cancel("Init cancelled.", { output: process.stderr });
|
|
745
|
+
process.exit(0);
|
|
746
|
+
}
|
|
747
|
+
const normalizedAgentName = agentName.trim();
|
|
748
|
+
if (!normalizedAgentName) throw new UserError4("Agent name cannot be empty.");
|
|
749
|
+
return { provider: parseProvider(provider), agentName: normalizedAgentName };
|
|
750
|
+
}
|
|
751
|
+
async function initCommand(options) {
|
|
716
752
|
const configPath = "agents.yaml";
|
|
717
753
|
if (await fileExists(configPath)) {
|
|
718
754
|
log.warn(`${configPath} already exists, skipping.`);
|
|
719
755
|
return;
|
|
720
756
|
}
|
|
721
757
|
p4.intro("agents init", { output: process.stderr });
|
|
722
|
-
const answers = await
|
|
723
|
-
|
|
724
|
-
provider: () => p4.select({
|
|
725
|
-
message: "Which provider(s) do you want to use?",
|
|
726
|
-
options: [
|
|
727
|
-
{ value: "bailian", label: "Bailian (\u963F\u91CC\u4E91\u767E\u70BC)" },
|
|
728
|
-
{ value: "claude", label: "Claude" },
|
|
729
|
-
{ value: "qoder", label: "Qoder" },
|
|
730
|
-
{ value: "ark", label: "Ark\uFF08\u706B\u5C71\u65B9\u821F\uFF09" },
|
|
731
|
-
{ value: "all", label: "All providers" }
|
|
732
|
-
],
|
|
733
|
-
output: process.stderr
|
|
734
|
-
}),
|
|
735
|
-
agentName: () => p4.text({
|
|
736
|
-
message: "Name your first agent:",
|
|
737
|
-
placeholder: "assistant",
|
|
738
|
-
defaultValue: "assistant",
|
|
739
|
-
output: process.stderr
|
|
740
|
-
})
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
onCancel: () => {
|
|
744
|
-
p4.cancel("Init cancelled.", { output: process.stderr });
|
|
745
|
-
process.exit(0);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
);
|
|
749
|
-
const template = buildTemplate({
|
|
750
|
-
provider: answers.provider,
|
|
751
|
-
agentName: answers.agentName
|
|
752
|
-
});
|
|
753
|
-
await writeFile(configPath, template, "utf8");
|
|
758
|
+
const answers = await promptForProjectOptions(options);
|
|
759
|
+
await writeFile(configPath, buildTemplate(answers), "utf8");
|
|
754
760
|
p4.log.success(`Created ${configPath}`, { output: process.stderr });
|
|
755
761
|
const gitignorePath = ".gitignore";
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
await writeFile(gitignorePath, `${GITIGNORE_ADDITIONS.trim()}
|
|
762
|
+
const currentGitignore = await fileExists(gitignorePath) ? await readFile(gitignorePath, "utf8") : "";
|
|
763
|
+
const existingPatterns = new Set(currentGitignore.split(/\r?\n/).map((line) => line.trim()));
|
|
764
|
+
const missingPatterns = LOCAL_GITIGNORE_PATTERNS.filter((pattern) => !existingPatterns.has(pattern));
|
|
765
|
+
if (missingPatterns.length > 0) {
|
|
766
|
+
const separator = currentGitignore.length === 0 || currentGitignore.endsWith("\n") ? "" : "\n";
|
|
767
|
+
const heading = currentGitignore.length === 0 ? "# agents\n" : "\n# agents\n";
|
|
768
|
+
await writeFile(gitignorePath, `${currentGitignore}${separator}${heading}${missingPatterns.join("\n")}
|
|
764
769
|
`, "utf8");
|
|
765
|
-
p4.log.success("Created
|
|
770
|
+
p4.log.success(`${currentGitignore.length === 0 ? "Created" : "Updated"} .gitignore`, {
|
|
771
|
+
output: process.stderr
|
|
772
|
+
});
|
|
766
773
|
}
|
|
767
|
-
p4.outro("Done! Next: edit agents.yaml, then run agents plan", {
|
|
768
|
-
output: process.stderr
|
|
769
|
-
});
|
|
774
|
+
p4.outro("Done! Next: edit agents.yaml, then run agents plan", { output: process.stderr });
|
|
770
775
|
}
|
|
771
776
|
|
|
772
777
|
// src/commands/memory.ts
|
|
@@ -785,13 +790,13 @@ import {
|
|
|
785
790
|
listMemoryStores,
|
|
786
791
|
listMemoryVersions,
|
|
787
792
|
redactMemoryVersion,
|
|
788
|
-
UserError as
|
|
793
|
+
UserError as UserError6,
|
|
789
794
|
updateMemory,
|
|
790
795
|
updateMemoryStore
|
|
791
796
|
} from "@openagentpack/sdk";
|
|
792
797
|
|
|
793
798
|
// src/runtime.ts
|
|
794
|
-
import { listProviderNames, UserError as
|
|
799
|
+
import { listProviderNames, UserError as UserError5 } from "@openagentpack/sdk";
|
|
795
800
|
import { Command, InvalidArgumentError, Option } from "commander";
|
|
796
801
|
var DEFAULT_CONFIG_FILE = "agents.yaml";
|
|
797
802
|
function isExplicitSource(source) {
|
|
@@ -826,13 +831,13 @@ function configFileArgs(args = process.argv.slice(2)) {
|
|
|
826
831
|
}
|
|
827
832
|
return values;
|
|
828
833
|
}
|
|
829
|
-
function configFileOption() {
|
|
830
|
-
return new Option("-f, --file <path>", "Config file path");
|
|
834
|
+
function configFileOption(options = {}) {
|
|
835
|
+
return new Option(options.short === false ? "--file <path>" : "-f, --file <path>", "Config file path");
|
|
831
836
|
}
|
|
832
837
|
function resolveConfigFile(command) {
|
|
833
838
|
const explicitFiles = [...new Set(configFileArgs())];
|
|
834
839
|
if (explicitFiles.length > 1) {
|
|
835
|
-
throw new
|
|
840
|
+
throw new UserError5(
|
|
836
841
|
`Conflicting config files supplied: ${explicitFiles.join(" and ")}. Use only one --file value.`
|
|
837
842
|
);
|
|
838
843
|
}
|
|
@@ -842,7 +847,7 @@ function resolveConfigFile(command) {
|
|
|
842
847
|
const localFile = command.getOptionValue("file");
|
|
843
848
|
const localSource = command.getOptionValueSource("file");
|
|
844
849
|
if (isExplicitSource(rootSource) && isExplicitSource(localSource) && rootFile && localFile && rootFile !== localFile) {
|
|
845
|
-
throw new
|
|
850
|
+
throw new UserError5(`Conflicting config files supplied: ${rootFile} and ${localFile}. Use only one --file value.`);
|
|
846
851
|
}
|
|
847
852
|
if (isExplicitSource(localSource) && localFile) return localFile;
|
|
848
853
|
if (rootFile) return rootFile;
|
|
@@ -900,15 +905,15 @@ function writeJsonLine(value) {
|
|
|
900
905
|
async function runtime(options) {
|
|
901
906
|
const ctx = await buildCliRuntime(options.file);
|
|
902
907
|
const provider = options.provider ?? (ctx.providers.size === 1 ? ctx.providers.keys().next().value : void 0);
|
|
903
|
-
if (!provider) throw new
|
|
908
|
+
if (!provider) throw new UserError6("Select a provider with --provider when multiple providers are configured.");
|
|
904
909
|
return { ctx, provider };
|
|
905
910
|
}
|
|
906
911
|
async function content(options) {
|
|
907
912
|
if (options.content !== void 0 && options.contentFile)
|
|
908
|
-
throw new
|
|
913
|
+
throw new UserError6("Use either --content or --content-file, not both.");
|
|
909
914
|
if (options.contentFile) return readFile2(options.contentFile, "utf8");
|
|
910
915
|
if (options.content !== void 0) return options.content;
|
|
911
|
-
throw new
|
|
916
|
+
throw new UserError6("Memory content is required; use --content or --content-file.");
|
|
912
917
|
}
|
|
913
918
|
async function memoryStoreListCommand(options) {
|
|
914
919
|
const { ctx, provider } = await runtime(options);
|
|
@@ -944,10 +949,10 @@ async function memoryCreateCommand(storeId, path, options) {
|
|
|
944
949
|
async function memoryBatchCreateCommand(storeId, inputFile, options) {
|
|
945
950
|
const { ctx, provider } = await runtime(options);
|
|
946
951
|
const parsed = JSON.parse(await readFile2(inputFile, "utf8"));
|
|
947
|
-
if (!Array.isArray(parsed)) throw new
|
|
952
|
+
if (!Array.isArray(parsed)) throw new UserError6("Batch input must be a JSON array of {path, content} objects.");
|
|
948
953
|
const items = parsed.map((item) => {
|
|
949
954
|
if (!item || typeof item !== "object" || typeof item.path !== "string" || typeof item.content !== "string") {
|
|
950
|
-
throw new
|
|
955
|
+
throw new UserError6("Every batch item must contain string path and content fields.");
|
|
951
956
|
}
|
|
952
957
|
return item;
|
|
953
958
|
});
|
|
@@ -1000,25 +1005,25 @@ async function memoryVersionRedactCommand(storeId, versionId, options) {
|
|
|
1000
1005
|
|
|
1001
1006
|
// src/commands/migrate.ts
|
|
1002
1007
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
1003
|
-
import { migrateConfig, UserError as
|
|
1008
|
+
import { migrateConfig, UserError as UserError7 } from "@openagentpack/sdk";
|
|
1004
1009
|
async function migrateCommand(options) {
|
|
1005
1010
|
const fromPath = options.from ?? "agents.synced.yaml";
|
|
1006
1011
|
const toPath = options.to ?? "agents.yaml";
|
|
1007
1012
|
const toExists = await fileExists(toPath);
|
|
1008
1013
|
if (!toExists) {
|
|
1009
|
-
throw new
|
|
1014
|
+
throw new UserError7(
|
|
1010
1015
|
`Target file '${toPath}' not found. Create a agents.yaml first (e.g. \`agents init\`), then run migrate.`
|
|
1011
1016
|
);
|
|
1012
1017
|
}
|
|
1013
1018
|
const result = await migrateConfig({ fromPath, toPath });
|
|
1014
1019
|
await writeFile2(toPath, result.yaml, "utf8");
|
|
1015
1020
|
const addedParts = [];
|
|
1016
|
-
for (const [
|
|
1017
|
-
addedParts.push(`${count} ${
|
|
1021
|
+
for (const [group, count] of Object.entries(result.added)) {
|
|
1022
|
+
addedParts.push(`${count} ${group}`);
|
|
1018
1023
|
}
|
|
1019
1024
|
const skippedParts = [];
|
|
1020
|
-
for (const [
|
|
1021
|
-
skippedParts.push(`${count} ${
|
|
1025
|
+
for (const [group, count] of Object.entries(result.skipped)) {
|
|
1026
|
+
skippedParts.push(`${count} ${group}`);
|
|
1022
1027
|
}
|
|
1023
1028
|
if (addedParts.length) {
|
|
1024
1029
|
log.success(`Migrated ${addedParts.join(", ")} into ${toPath}.`);
|
|
@@ -1119,9 +1124,11 @@ function formatPrice(factor) {
|
|
|
1119
1124
|
}
|
|
1120
1125
|
|
|
1121
1126
|
// src/commands/plan.ts
|
|
1122
|
-
import { UserError as
|
|
1127
|
+
import { UserError as UserError8 } from "@openagentpack/sdk";
|
|
1128
|
+
import { assertLegacyYamlNotShadowed as assertLegacyYamlNotShadowed3 } from "@openagentpack/sdk/project-workspace";
|
|
1123
1129
|
import chalk8 from "chalk";
|
|
1124
1130
|
async function planCommand(options) {
|
|
1131
|
+
await assertLegacyYamlNotShadowed3(options.file);
|
|
1125
1132
|
const ctx = await buildCliRuntime(options.file);
|
|
1126
1133
|
assertProviderConfigured(ctx, options.provider);
|
|
1127
1134
|
const { plan } = await planProjectWithRefresh(ctx, {
|
|
@@ -1132,13 +1139,13 @@ async function planCommand(options) {
|
|
|
1132
1139
|
if (options.json) {
|
|
1133
1140
|
writeJson(plan);
|
|
1134
1141
|
if (plan.diagnostics.some((d) => d.severity === "error")) {
|
|
1135
|
-
throw new
|
|
1142
|
+
throw new UserError8("Plan contains errors.");
|
|
1136
1143
|
}
|
|
1137
1144
|
return;
|
|
1138
1145
|
}
|
|
1139
1146
|
renderDiagnostics(plan.diagnostics);
|
|
1140
1147
|
if (diagnosticsHaveErrors(plan.diagnostics)) {
|
|
1141
|
-
throw new
|
|
1148
|
+
throw new UserError8("Plan contains errors.");
|
|
1142
1149
|
}
|
|
1143
1150
|
const creates = plan.actions.filter((a) => a.action === "create");
|
|
1144
1151
|
const updates = plan.actions.filter((a) => a.action === "update");
|
|
@@ -1191,16 +1198,15 @@ Plan: ${chalk8.green(`${creates.length} to create`)}, ${chalk8.yellow(`${updates
|
|
|
1191
1198
|
|
|
1192
1199
|
// src/commands/playground.ts
|
|
1193
1200
|
import { spawn } from "child_process";
|
|
1201
|
+
import { createHash, randomBytes } from "crypto";
|
|
1194
1202
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
1195
1203
|
import { createRequire } from "module";
|
|
1196
1204
|
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
1197
1205
|
import { fileURLToPath } from "url";
|
|
1198
|
-
import { AGENTS_CONFIG_PROVIDERS } from "@openagentpack/sdk";
|
|
1199
1206
|
var CLI_PKG = "@openagentpack/cli";
|
|
1200
1207
|
var PLAYGROUND_PKG = "@openagentpack/playground";
|
|
1201
1208
|
var DEFAULT_PORT = 4848;
|
|
1202
1209
|
var PLAYGROUND_URL_RE = /running at http:\/\/localhost:(\d+)/;
|
|
1203
|
-
var SUPPORTED_PLAYGROUND_PROVIDERS = new Set(AGENTS_CONFIG_PROVIDERS);
|
|
1204
1210
|
function cliVersion() {
|
|
1205
1211
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
1206
1212
|
const candidates = [
|
|
@@ -1255,7 +1261,7 @@ function watchPlaygroundPort(stdout, onPort) {
|
|
|
1255
1261
|
onPort(Number(match[1]));
|
|
1256
1262
|
});
|
|
1257
1263
|
}
|
|
1258
|
-
async function waitForPlaygroundReady(child, fallbackPort, timeoutMs) {
|
|
1264
|
+
async function waitForPlaygroundReady(child, fallbackPort, timeoutMs, expectedProjectId) {
|
|
1259
1265
|
let port = fallbackPort;
|
|
1260
1266
|
let settled = false;
|
|
1261
1267
|
return new Promise((resolve6) => {
|
|
@@ -1274,7 +1280,8 @@ async function waitForPlaygroundReady(child, fallbackPort, timeoutMs) {
|
|
|
1274
1280
|
}
|
|
1275
1281
|
try {
|
|
1276
1282
|
const res = await fetch(`http://localhost:${port}/health`);
|
|
1277
|
-
|
|
1283
|
+
const body = res.ok ? await res.json() : void 0;
|
|
1284
|
+
if (body?.playground?.project_id === expectedProjectId) {
|
|
1278
1285
|
settled = true;
|
|
1279
1286
|
resolve6(port);
|
|
1280
1287
|
return;
|
|
@@ -1295,6 +1302,50 @@ function openBrowser(url) {
|
|
|
1295
1302
|
log.warn(`Could not open a browser automatically \u2014 visit ${url}`);
|
|
1296
1303
|
}
|
|
1297
1304
|
}
|
|
1305
|
+
function playgroundBrowserTargetFromSummary(baseUrl, summary, requestedAgent) {
|
|
1306
|
+
if (summary.status !== "valid") return { url: baseUrl };
|
|
1307
|
+
const agentIds = (summary.agents ?? []).map((entry) => entry.agent?.id?.trim()).filter((agentId) => Boolean(agentId));
|
|
1308
|
+
const requested = requestedAgent?.trim();
|
|
1309
|
+
if (requested) {
|
|
1310
|
+
if (agentIds.includes(requested)) {
|
|
1311
|
+
return { url: `${baseUrl}/agents/${encodeURIComponent(requested)}/preview` };
|
|
1312
|
+
}
|
|
1313
|
+
return {
|
|
1314
|
+
url: baseUrl,
|
|
1315
|
+
warning: `Agent '${requested}' was not found. Opening the project workbench instead.`
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
if (agentIds.length === 1) {
|
|
1319
|
+
return { url: `${baseUrl}/agents/${encodeURIComponent(agentIds[0])}/preview` };
|
|
1320
|
+
}
|
|
1321
|
+
if (agentIds.length > 1) {
|
|
1322
|
+
return {
|
|
1323
|
+
url: baseUrl,
|
|
1324
|
+
warning: "This project declares multiple Agents. Opening the workbench; rerun with --agent <id> for Preview."
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
return { url: baseUrl };
|
|
1328
|
+
}
|
|
1329
|
+
async function resolvePlaygroundBrowserTarget(port, requestedAgent) {
|
|
1330
|
+
const baseUrl = `http://localhost:${port}`;
|
|
1331
|
+
try {
|
|
1332
|
+
const response = await fetch(`${baseUrl}/api/project`, { signal: AbortSignal.timeout(3e3) });
|
|
1333
|
+
if (!response.ok) return { url: baseUrl };
|
|
1334
|
+
return playgroundBrowserTargetFromSummary(
|
|
1335
|
+
baseUrl,
|
|
1336
|
+
await response.json(),
|
|
1337
|
+
requestedAgent
|
|
1338
|
+
);
|
|
1339
|
+
} catch {
|
|
1340
|
+
return { url: baseUrl };
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
async function openPlaygroundSurface(port, surface, options) {
|
|
1344
|
+
if (options.open === false) return;
|
|
1345
|
+
const target = surface === "workbench" ? { url: `http://localhost:${port}` } : await resolvePlaygroundBrowserTarget(port, options.agent);
|
|
1346
|
+
if (target.warning) log.warn(target.warning);
|
|
1347
|
+
openBrowser(target.url);
|
|
1348
|
+
}
|
|
1298
1349
|
async function probeExistingPlayground(port) {
|
|
1299
1350
|
try {
|
|
1300
1351
|
const controller = new AbortController();
|
|
@@ -1304,7 +1355,11 @@ async function probeExistingPlayground(port) {
|
|
|
1304
1355
|
if (!res.ok) return null;
|
|
1305
1356
|
const body = await res.json();
|
|
1306
1357
|
if (body.playground?.pid) {
|
|
1307
|
-
return {
|
|
1358
|
+
return {
|
|
1359
|
+
version: body.playground.version ?? "unknown",
|
|
1360
|
+
pid: body.playground.pid,
|
|
1361
|
+
projectId: body.playground.project_id
|
|
1362
|
+
};
|
|
1308
1363
|
}
|
|
1309
1364
|
} catch {
|
|
1310
1365
|
}
|
|
@@ -1324,22 +1379,20 @@ async function replaceExistingPlayground(existing, port) {
|
|
|
1324
1379
|
}
|
|
1325
1380
|
return false;
|
|
1326
1381
|
}
|
|
1327
|
-
async function
|
|
1382
|
+
async function launchPlayground(options, surface) {
|
|
1328
1383
|
const port = options.port ? Number(options.port) : DEFAULT_PORT;
|
|
1329
1384
|
if (!Number.isInteger(port) || port <= 0) {
|
|
1330
1385
|
throw new Error(`Invalid --port '${options.port}'`);
|
|
1331
1386
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
throw new Error(`Playground supports providers: ${supported}; received '${options.provider}'.`);
|
|
1335
|
-
}
|
|
1387
|
+
const sourcePath = surface === "workbench" ? resolve2(options.project ?? ".") : resolve2(options.file ?? "agents.yaml");
|
|
1388
|
+
const projectId = createHash("sha256").update(sourcePath).digest("hex").slice(0, 16);
|
|
1336
1389
|
const version = cliVersion();
|
|
1337
1390
|
const existing = await probeExistingPlayground(port);
|
|
1338
1391
|
if (existing) {
|
|
1339
|
-
if (existing.version === version) {
|
|
1340
|
-
const
|
|
1341
|
-
log.success(`Playground v${version} already running at ${
|
|
1342
|
-
|
|
1392
|
+
if (existing.version === version && existing.projectId === projectId) {
|
|
1393
|
+
const baseUrl2 = `http://localhost:${port}`;
|
|
1394
|
+
log.success(`Playground v${version} already running at ${baseUrl2} (pid ${existing.pid})`);
|
|
1395
|
+
await openPlaygroundSurface(port, surface, options);
|
|
1343
1396
|
return;
|
|
1344
1397
|
}
|
|
1345
1398
|
const freed = await replaceExistingPlayground(existing, port);
|
|
@@ -1350,10 +1403,17 @@ async function playgroundCommand(options) {
|
|
|
1350
1403
|
return;
|
|
1351
1404
|
}
|
|
1352
1405
|
}
|
|
1353
|
-
const env = {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1406
|
+
const env = {
|
|
1407
|
+
...process.env,
|
|
1408
|
+
PORT: String(port),
|
|
1409
|
+
AGENTS_PLAYGROUND_TOKEN: randomBytes(32).toString("hex")
|
|
1410
|
+
};
|
|
1411
|
+
if (surface === "workbench") {
|
|
1412
|
+
delete env.AGENTS_CONFIG_PATH;
|
|
1413
|
+
env.AGENTS_PROJECT_ROOT = sourcePath;
|
|
1414
|
+
} else {
|
|
1415
|
+
delete env.AGENTS_PROJECT_ROOT;
|
|
1416
|
+
env.AGENTS_CONFIG_PATH = sourcePath;
|
|
1357
1417
|
}
|
|
1358
1418
|
const { cmd, args } = resolveLauncher(version);
|
|
1359
1419
|
if (cmd === "npx") log.info(`Fetching ${PLAYGROUND_PKG} (first run may take a moment)...`);
|
|
@@ -1366,14 +1426,274 @@ async function playgroundCommand(options) {
|
|
|
1366
1426
|
log.error(`Failed to start playground: ${err instanceof Error ? err.message : String(err)}`);
|
|
1367
1427
|
process.exit(1);
|
|
1368
1428
|
});
|
|
1369
|
-
const readyPort = await waitForPlaygroundReady(child, port, 3e4);
|
|
1429
|
+
const readyPort = await waitForPlaygroundReady(child, port, 3e4, projectId);
|
|
1370
1430
|
if (readyPort === null) {
|
|
1371
1431
|
log.warn(`Playground did not become ready in time \u2014 check the logs above, then open http://localhost:${port}`);
|
|
1372
1432
|
return;
|
|
1373
1433
|
}
|
|
1374
|
-
const
|
|
1375
|
-
log.success(`Playground ready at ${
|
|
1376
|
-
|
|
1434
|
+
const baseUrl = `http://localhost:${readyPort}`;
|
|
1435
|
+
log.success(`Playground ready at ${baseUrl}`);
|
|
1436
|
+
await openPlaygroundSurface(readyPort, surface, options);
|
|
1437
|
+
}
|
|
1438
|
+
async function playgroundCommand(options) {
|
|
1439
|
+
await launchPlayground(options, "preview");
|
|
1440
|
+
}
|
|
1441
|
+
async function workbenchCommand(options) {
|
|
1442
|
+
await launchPlayground(options, "workbench");
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// src/commands/project.ts
|
|
1446
|
+
import * as prompts from "@clack/prompts";
|
|
1447
|
+
import { UserError as UserError9 } from "@openagentpack/sdk";
|
|
1448
|
+
import {
|
|
1449
|
+
commitProjectBuild,
|
|
1450
|
+
createDirectoryWorkspaceVersionService,
|
|
1451
|
+
executeProjectPublish,
|
|
1452
|
+
initializeDirectoryProject,
|
|
1453
|
+
planProjectPublish,
|
|
1454
|
+
previewProjectBuild,
|
|
1455
|
+
validateDirectoryProject
|
|
1456
|
+
} from "@openagentpack/sdk/project-workspace";
|
|
1457
|
+
import chalk9 from "chalk";
|
|
1458
|
+
function projectRoot(options) {
|
|
1459
|
+
return options.project ?? ".";
|
|
1460
|
+
}
|
|
1461
|
+
async function projectInitCommand(options) {
|
|
1462
|
+
const result = await initializeDirectoryProject({ projectRoot: projectRoot(options) });
|
|
1463
|
+
if (options.json) return writeJson(result);
|
|
1464
|
+
log.success(
|
|
1465
|
+
result.converted_from_yaml ? "Converted agents.yaml into a directory project." : "Created directory project."
|
|
1466
|
+
);
|
|
1467
|
+
if (result.state_migrated) log.success("Copied agents.state.json to .openagentpack/state.json.");
|
|
1468
|
+
console.log(`Project: ${result.project_root}`);
|
|
1469
|
+
console.log(`Baseline version: ${result.baseline_version}`);
|
|
1470
|
+
}
|
|
1471
|
+
async function projectValidateCommand(options) {
|
|
1472
|
+
const inspection = await validateDirectoryProject(projectRoot(options));
|
|
1473
|
+
if (options.json) return writeJson(inspection);
|
|
1474
|
+
renderDiagnostics([...inspection.diagnostics, ...inspection.warnings]);
|
|
1475
|
+
if (inspection.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
1476
|
+
throw new UserError9("Directory project is invalid.");
|
|
1477
|
+
}
|
|
1478
|
+
log.success(`Project is valid (${inspection.project_revision.slice(0, 12)}).`);
|
|
1479
|
+
}
|
|
1480
|
+
async function projectBuildCommand(options) {
|
|
1481
|
+
const preview = await previewProjectBuild(projectRoot(options));
|
|
1482
|
+
if (options.json) {
|
|
1483
|
+
if (options.dryRun) return writeJson(preview);
|
|
1484
|
+
}
|
|
1485
|
+
if (!options.json) renderDiagnostics([...preview.diagnostics, ...preview.warnings]);
|
|
1486
|
+
if (!preview.can_build) throw new UserError9("Directory project is invalid and cannot be built.");
|
|
1487
|
+
if (!options.json) {
|
|
1488
|
+
const sourcePreview = await loadBuildSourcePreview(preview.project_root);
|
|
1489
|
+
if (sourcePreview.projectRevision && sourcePreview.projectRevision !== preview.project_revision) {
|
|
1490
|
+
throw new UserError9("Project source changed. Preview Build again.");
|
|
1491
|
+
}
|
|
1492
|
+
renderBuildSourcePreview(sourcePreview);
|
|
1493
|
+
for (const move of preview.organization_moves) {
|
|
1494
|
+
console.log(
|
|
1495
|
+
chalk9.yellow(`move ${move.resource_type}.${move.resource_id}: ${move.from} -> ${move.to} (${move.reason})`)
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
if (options.dryRun) return;
|
|
1500
|
+
if (!options.yes) {
|
|
1501
|
+
const confirmed = await prompts.confirm({
|
|
1502
|
+
message: "Write the Build, inferred resource associations, and Vault secret references (.env)?",
|
|
1503
|
+
output: process.stderr
|
|
1504
|
+
});
|
|
1505
|
+
if (prompts.isCancel(confirmed) || !confirmed) {
|
|
1506
|
+
prompts.cancel("Build cancelled. Project files were not changed.", { output: process.stderr });
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
const result = await commitProjectBuild({
|
|
1511
|
+
projectRoot: preview.project_root,
|
|
1512
|
+
baseRevision: preview.project_revision
|
|
1513
|
+
});
|
|
1514
|
+
if (options.json) return writeJson(result.manifest);
|
|
1515
|
+
log.success(`Build created at .openagentpack/build/agents.yaml (${result.manifest.yaml_hash.slice(0, 12)}).`);
|
|
1516
|
+
}
|
|
1517
|
+
async function projectPublishCommand(options) {
|
|
1518
|
+
ensureCredentials();
|
|
1519
|
+
const preview = await planProjectPublish(projectRoot(options), {
|
|
1520
|
+
provider: options.provider,
|
|
1521
|
+
refresh: options.refresh,
|
|
1522
|
+
onFeedback: options.json ? void 0 : renderRuntimeFeedback,
|
|
1523
|
+
quiet: options.json
|
|
1524
|
+
});
|
|
1525
|
+
if (!options.json) renderDiagnostics(preview.planned.plan.diagnostics);
|
|
1526
|
+
if (preview.planned.plan.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
1527
|
+
throw new UserError9("Cannot Publish: resolve the plan errors first.");
|
|
1528
|
+
}
|
|
1529
|
+
const actions = preview.planned.plan.actions.filter((action) => action.action !== "no-op");
|
|
1530
|
+
if (!options.json) {
|
|
1531
|
+
for (const action of actions) {
|
|
1532
|
+
const color = action.action === "create" ? chalk9.green : action.action === "update" ? chalk9.yellow : chalk9.red;
|
|
1533
|
+
console.log(
|
|
1534
|
+
color(
|
|
1535
|
+
`${action.action === "create" ? "+" : action.action === "update" ? "~" : "-"} ${formatResourceLabel(action.address)}`
|
|
1536
|
+
)
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
if (!options.yes) {
|
|
1541
|
+
const confirmed = await prompts.confirm({
|
|
1542
|
+
message: actions.length === 0 ? "Publish this Build and record its version?" : `Publish ${actions.length} remote change(s)?`,
|
|
1543
|
+
output: process.stderr
|
|
1544
|
+
});
|
|
1545
|
+
if (prompts.isCancel(confirmed) || !confirmed) {
|
|
1546
|
+
prompts.cancel("Publish cancelled. No remote resources were changed.", { output: process.stderr });
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
const result = await executeProjectPublish({
|
|
1551
|
+
projectRoot: preview.project_root,
|
|
1552
|
+
expectedProjectRevision: preview.project_revision,
|
|
1553
|
+
expectedYamlHash: preview.build_manifest.yaml_hash,
|
|
1554
|
+
expectedPlanFingerprint: preview.plan_fingerprint,
|
|
1555
|
+
provider: options.provider,
|
|
1556
|
+
refresh: options.refresh,
|
|
1557
|
+
concurrency: options.concurrency,
|
|
1558
|
+
policy: "force",
|
|
1559
|
+
onFeedback: options.json ? void 0 : renderRuntimeFeedback
|
|
1560
|
+
});
|
|
1561
|
+
if (options.json) return writeJson(result);
|
|
1562
|
+
const successful = result.execution?.results.filter((entry) => entry.status === "success").length ?? 0;
|
|
1563
|
+
log.success(`Publish complete. ${successful} remote action(s) succeeded.`);
|
|
1564
|
+
if (result.version) log.success(`Created project version ${result.version.short_version}.`);
|
|
1565
|
+
if (result.working_tree_changed)
|
|
1566
|
+
log.warn("Project files changed during Publish. The published Build is preserved, but a new Build is required.");
|
|
1567
|
+
}
|
|
1568
|
+
async function projectWorkbenchCommand(options) {
|
|
1569
|
+
await workbenchCommand({ project: projectRoot(options), port: options.port, open: options.open });
|
|
1570
|
+
}
|
|
1571
|
+
async function projectVersionEnableCommand(options) {
|
|
1572
|
+
const result = await createDirectoryWorkspaceVersionService(projectRoot(options)).enable(
|
|
1573
|
+
"Initialize project versions"
|
|
1574
|
+
);
|
|
1575
|
+
if (options.json) return writeJson(result);
|
|
1576
|
+
console.log(
|
|
1577
|
+
result.version ? `Created baseline ${result.version.short_version}.` : "Current project source is already versioned."
|
|
1578
|
+
);
|
|
1579
|
+
console.log("Project versioning is enabled.");
|
|
1580
|
+
}
|
|
1581
|
+
async function projectVersionDisableCommand(options) {
|
|
1582
|
+
const result = await createDirectoryWorkspaceVersionService(projectRoot(options)).disable();
|
|
1583
|
+
if (options.json) return writeJson(result);
|
|
1584
|
+
console.log("Project versioning is disabled. Existing versions were kept.");
|
|
1585
|
+
}
|
|
1586
|
+
async function projectVersionStatusCommand(options) {
|
|
1587
|
+
const status = await createDirectoryWorkspaceVersionService(projectRoot(options)).status();
|
|
1588
|
+
if (options.json) return writeJson(status);
|
|
1589
|
+
console.log(`Versioning: ${status.enabled ? "enabled" : "disabled"}`);
|
|
1590
|
+
console.log(`Head: ${status.head_version ?? "none"}`);
|
|
1591
|
+
console.log(`Source: ${status.source_status}`);
|
|
1592
|
+
console.log(`Store: ${status.store_root}`);
|
|
1593
|
+
for (const blocker of [.../* @__PURE__ */ new Set([...status.write_blockers, ...status.restore_blockers])])
|
|
1594
|
+
console.log(chalk9.yellow(`Blocker: ${blocker}`));
|
|
1595
|
+
}
|
|
1596
|
+
async function projectVersionListCommand(options) {
|
|
1597
|
+
const page = await createDirectoryWorkspaceVersionService(projectRoot(options)).listVersions({
|
|
1598
|
+
limit: options.limit,
|
|
1599
|
+
cursor: options.cursor
|
|
1600
|
+
});
|
|
1601
|
+
if (options.json) return writeJson(page);
|
|
1602
|
+
for (const version of page.versions)
|
|
1603
|
+
console.log(
|
|
1604
|
+
`${chalk9.yellow(version.short_version)} ${version.created_at} ${version.message} ${chalk9.dim(`(${version.created_by})`)}`
|
|
1605
|
+
);
|
|
1606
|
+
if (page.versions.length === 0) console.log("No project versions exist.");
|
|
1607
|
+
if (page.next_cursor) console.log(chalk9.dim(`Next cursor: ${page.next_cursor}`));
|
|
1608
|
+
}
|
|
1609
|
+
async function projectVersionPreviewCommand(version, options) {
|
|
1610
|
+
const preview = await createDirectoryWorkspaceVersionService(projectRoot(options)).previewVersion(version);
|
|
1611
|
+
if (options.json) return writeJson(preview);
|
|
1612
|
+
renderDirectoryVersionPreview(preview);
|
|
1613
|
+
}
|
|
1614
|
+
async function projectVersionRestoreCommand(version, options) {
|
|
1615
|
+
const service = createDirectoryWorkspaceVersionService(projectRoot(options));
|
|
1616
|
+
const preview = await service.previewVersion(version);
|
|
1617
|
+
if (!options.json) renderDirectoryVersionPreview(preview);
|
|
1618
|
+
if (!preview.can_restore)
|
|
1619
|
+
throw new UserError9(preview.blockers[0] ?? preview.diagnostics[0]?.message ?? "Version cannot be restored.");
|
|
1620
|
+
if (!options.yes) {
|
|
1621
|
+
const confirmed = await prompts.confirm({
|
|
1622
|
+
message: "Restore this version to the project working directory?",
|
|
1623
|
+
output: process.stderr
|
|
1624
|
+
});
|
|
1625
|
+
if (prompts.isCancel(confirmed) || !confirmed) return;
|
|
1626
|
+
}
|
|
1627
|
+
const restored = await service.restoreVersion(version, {
|
|
1628
|
+
headVersion: preview.base_head_version,
|
|
1629
|
+
projectRevision: preview.base_project_revision
|
|
1630
|
+
});
|
|
1631
|
+
if (options.json) return writeJson(restored);
|
|
1632
|
+
log.success(
|
|
1633
|
+
`Restored ${version.slice(0, 12)} to the working directory. Version head and remote State were not changed.`
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
function renderDirectoryVersionPreview(preview) {
|
|
1637
|
+
console.log(chalk9.bold(`Version ${preview.version_id}`));
|
|
1638
|
+
for (const change of preview.changes) {
|
|
1639
|
+
const color = change.change === "create" ? chalk9.green : change.change === "delete" ? chalk9.red : chalk9.yellow;
|
|
1640
|
+
console.log(color(`${change.change} ${change.path}${change.binary ? " (binary)" : ""}`));
|
|
1641
|
+
if (!change.binary) renderSimpleDiff(change.before ?? "", change.after ?? "");
|
|
1642
|
+
}
|
|
1643
|
+
renderDiagnostics(preview.diagnostics);
|
|
1644
|
+
for (const blocker of preview.blockers) console.log(chalk9.yellow(`Blocker: ${blocker}`));
|
|
1645
|
+
console.log(`Can restore: ${preview.can_restore ? "yes" : "no"}`);
|
|
1646
|
+
}
|
|
1647
|
+
async function loadBuildSourcePreview(projectRoot2) {
|
|
1648
|
+
try {
|
|
1649
|
+
const service = createDirectoryWorkspaceVersionService(projectRoot2);
|
|
1650
|
+
const status = await service.status();
|
|
1651
|
+
if (!status.head_version) return { baselineVersion: null, projectRevision: status.project_revision, changes: [] };
|
|
1652
|
+
const preview = await service.previewVersion(status.head_version);
|
|
1653
|
+
return {
|
|
1654
|
+
baselineVersion: status.head_version,
|
|
1655
|
+
projectRevision: preview.base_project_revision,
|
|
1656
|
+
changes: preview.changes
|
|
1657
|
+
};
|
|
1658
|
+
} catch (error) {
|
|
1659
|
+
return {
|
|
1660
|
+
baselineVersion: null,
|
|
1661
|
+
changes: [],
|
|
1662
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
function renderBuildSourcePreview(preview) {
|
|
1667
|
+
console.log(chalk9.bold("Project source changes"));
|
|
1668
|
+
if (preview.error) {
|
|
1669
|
+
console.log(chalk9.yellow(` Source Diff unavailable: ${preview.error}`));
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
if (!preview.baselineVersion) {
|
|
1673
|
+
console.log(chalk9.dim(" Source Diff unavailable: no project version baseline exists."));
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
console.log(chalk9.dim(` baseline ${preview.baselineVersion.slice(0, 12)} -> working tree`));
|
|
1677
|
+
if (preview.changes.length === 0) {
|
|
1678
|
+
console.log(chalk9.dim(" no source changes"));
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
for (const change of preview.changes) {
|
|
1682
|
+
const displayedChange = change.change === "create" ? "delete" : change.change === "delete" ? "create" : "update";
|
|
1683
|
+
const color = displayedChange === "create" ? chalk9.green : displayedChange === "delete" ? chalk9.red : chalk9.yellow;
|
|
1684
|
+
console.log(color(`${displayedChange} ${change.path}${change.binary ? " (binary)" : ""}`));
|
|
1685
|
+
if (!change.binary) renderSimpleDiff(change.after ?? "", change.before ?? "");
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
function renderSimpleDiff(before, after) {
|
|
1689
|
+
if (before === after) {
|
|
1690
|
+
console.log(chalk9.dim(" no content changes"));
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
const beforeLines = before.split("\n");
|
|
1694
|
+
const afterLines = after.split("\n");
|
|
1695
|
+
for (const line of beforeLines) if (!afterLines.includes(line)) console.log(chalk9.red(`-${line}`));
|
|
1696
|
+
for (const line of afterLines) if (!beforeLines.includes(line)) console.log(chalk9.green(`+${line}`));
|
|
1377
1697
|
}
|
|
1378
1698
|
|
|
1379
1699
|
// src/commands/session.ts
|
|
@@ -1388,10 +1708,10 @@ import {
|
|
|
1388
1708
|
sendSessionMessageStreaming,
|
|
1389
1709
|
startSessionRun,
|
|
1390
1710
|
startSessionRunPolling,
|
|
1391
|
-
UserError as
|
|
1711
|
+
UserError as UserError10
|
|
1392
1712
|
} from "@openagentpack/sdk";
|
|
1393
1713
|
import { sanitizeSessionEvent, sanitizeSessionEvents } from "@openagentpack/sdk/session-events";
|
|
1394
|
-
import
|
|
1714
|
+
import chalk10 from "chalk";
|
|
1395
1715
|
function formatTimestamp(iso) {
|
|
1396
1716
|
const d = new Date(iso);
|
|
1397
1717
|
if (Number.isNaN(d.getTime())) return iso;
|
|
@@ -1413,7 +1733,7 @@ async function sessionCreateCommand(agentNameOrOptions, maybeOptions) {
|
|
|
1413
1733
|
const options = maybeOptions ?? agentNameOrOptions;
|
|
1414
1734
|
const positionalAgent = typeof agentNameOrOptions === "string" ? agentNameOrOptions : void 0;
|
|
1415
1735
|
if (positionalAgent && options.agent && positionalAgent !== options.agent) {
|
|
1416
|
-
throw new
|
|
1736
|
+
throw new UserError10("Specify agent either positionally or with --agent, not both.");
|
|
1417
1737
|
}
|
|
1418
1738
|
const ctx = await buildCliRuntime(options.file);
|
|
1419
1739
|
const run = await createSessionForAgent(ctx, {
|
|
@@ -1429,7 +1749,7 @@ async function sessionCreateCommand(agentNameOrOptions, maybeOptions) {
|
|
|
1429
1749
|
title: options.title
|
|
1430
1750
|
});
|
|
1431
1751
|
const { agentName, session } = run;
|
|
1432
|
-
log.success(`Session created: ${
|
|
1752
|
+
log.success(`Session created: ${chalk10.bold(session.id)}`);
|
|
1433
1753
|
console.log(` Agent: ${agentName}`);
|
|
1434
1754
|
console.log(` Environment: ${session.environment_id}`);
|
|
1435
1755
|
if (session.tunnel_id) console.log(` Tunnel: ${session.tunnel_id}`);
|
|
@@ -1476,10 +1796,10 @@ async function sessionListCommand(options) {
|
|
|
1476
1796
|
const title = (s.title ?? "").slice(0, 18).padEnd(20);
|
|
1477
1797
|
const agent = (agentNameMap.get(s.id) ?? s.agent_id.slice(0, 12)).padEnd(14);
|
|
1478
1798
|
const statusText = s.status.padEnd(12);
|
|
1479
|
-
const status = s.status === "idle" ?
|
|
1799
|
+
const status = s.status === "idle" ? chalk10.green(statusText) : s.status === "processing" ? chalk10.yellow(statusText) : s.status === "failed" ? chalk10.red(statusText) : chalk10.gray(statusText);
|
|
1480
1800
|
const created = formatTimestamp(s.created_at).padEnd(20);
|
|
1481
1801
|
const duration = formatDuration(s.created_at, s.status === "idle" ? s.updated_at : void 0);
|
|
1482
|
-
printTableRow([
|
|
1802
|
+
printTableRow([chalk10.bold(id), title, chalk10.cyan(agent), status, chalk10.dim(created), duration]);
|
|
1483
1803
|
}
|
|
1484
1804
|
printTableFooter();
|
|
1485
1805
|
if (hasMore) {
|
|
@@ -1489,7 +1809,7 @@ async function sessionListCommand(options) {
|
|
|
1489
1809
|
async function sessionGetCommand(sessionId, options) {
|
|
1490
1810
|
const ctx = await buildCliRuntime(options.file);
|
|
1491
1811
|
const session = await getSession(ctx, sessionId, options.provider);
|
|
1492
|
-
console.log(` ID: ${
|
|
1812
|
+
console.log(` ID: ${chalk10.bold(session.id)}`);
|
|
1493
1813
|
console.log(` Agent: ${session.agent_id}`);
|
|
1494
1814
|
console.log(` Environment: ${session.environment_id}`);
|
|
1495
1815
|
if (session.tunnel_id) console.log(` Tunnel: ${session.tunnel_id}`);
|
|
@@ -1510,7 +1830,7 @@ function shouldRenderLiveEvent(event) {
|
|
|
1510
1830
|
}
|
|
1511
1831
|
function renderTerminalStatus(status, json) {
|
|
1512
1832
|
if (json) return;
|
|
1513
|
-
const color = status === "idle" || status === "completed" ?
|
|
1833
|
+
const color = status === "idle" || status === "completed" ? chalk10.green : chalk10.red;
|
|
1514
1834
|
log.plain(color(`
|
|
1515
1835
|
[session ${status}]`));
|
|
1516
1836
|
}
|
|
@@ -1524,17 +1844,17 @@ function renderEvent(event) {
|
|
|
1524
1844
|
if (event.type === "message" && event.content) {
|
|
1525
1845
|
process.stdout.write(event.content);
|
|
1526
1846
|
} else if (event.type === "tool_use") {
|
|
1527
|
-
log.plain(
|
|
1847
|
+
log.plain(chalk10.cyan(`
|
|
1528
1848
|
[tool] ${event.tool_name}`));
|
|
1529
1849
|
} else if (event.type === "tool_result" && event.content) {
|
|
1530
1850
|
const preview = event.content.length > 200 ? `${event.content.slice(0, 200)}...` : event.content;
|
|
1531
|
-
log.plain(
|
|
1851
|
+
log.plain(chalk10.dim(preview));
|
|
1532
1852
|
} else if (event.type === "status") {
|
|
1533
1853
|
if (event.status === "running") {
|
|
1534
|
-
log.plain(
|
|
1854
|
+
log.plain(chalk10.yellow("\n[session running]"));
|
|
1535
1855
|
}
|
|
1536
1856
|
} else if (event.type === "error") {
|
|
1537
|
-
log.plain(
|
|
1857
|
+
log.plain(chalk10.red(`
|
|
1538
1858
|
[error] ${event.content ?? "unknown error"}`));
|
|
1539
1859
|
}
|
|
1540
1860
|
}
|
|
@@ -1570,7 +1890,7 @@ async function sessionRunCommand(promptOrAgent, promptOrOptions, maybeOptions) {
|
|
|
1570
1890
|
const prompt = hasPositionalAgent ? promptOrOptions : promptOrAgent;
|
|
1571
1891
|
const options = hasPositionalAgent ? maybeOptions : promptOrOptions ?? maybeOptions;
|
|
1572
1892
|
if (positionalAgent && options.agent && positionalAgent !== options.agent) {
|
|
1573
|
-
throw new
|
|
1893
|
+
throw new UserError10("Specify agent either positionally or with --agent, not both.");
|
|
1574
1894
|
}
|
|
1575
1895
|
const runOptions = {
|
|
1576
1896
|
agent: positionalAgent ?? options.agent,
|
|
@@ -1589,7 +1909,7 @@ async function sessionRunCommand(promptOrAgent, promptOrOptions, maybeOptions) {
|
|
|
1589
1909
|
const run = stream ? await startSessionRun(ctx, prompt, runOptions) : await startSessionRunPolling(ctx, prompt, runOptions);
|
|
1590
1910
|
const session = run.session;
|
|
1591
1911
|
if (!options.json) {
|
|
1592
|
-
log.success(`Session created: ${
|
|
1912
|
+
log.success(`Session created: ${chalk10.bold(session.id)}`);
|
|
1593
1913
|
}
|
|
1594
1914
|
if (stream) {
|
|
1595
1915
|
await streamAndRender(run.events, !!options.json);
|
|
@@ -1641,10 +1961,10 @@ async function sessionEventsCommand(sessionId, options) {
|
|
|
1641
1961
|
else if (e.type === "tool_result") preview = (e.content ?? "").slice(0, 60);
|
|
1642
1962
|
else if (e.type === "status") preview = `${e.status ?? ""}${e.stop_reason ? ` (${e.stop_reason})` : ""}`;
|
|
1643
1963
|
else if (e.type === "error") preview = (e.content ?? "").slice(0, 60);
|
|
1644
|
-
else if (e.type === "thinking") preview =
|
|
1964
|
+
else if (e.type === "thinking") preview = chalk10.dim("(thinking)");
|
|
1645
1965
|
else preview = e.raw_type;
|
|
1646
|
-
const typeColor = e.type === "error" ?
|
|
1647
|
-
printTableRow([
|
|
1966
|
+
const typeColor = e.type === "error" ? chalk10.red(typeLabel) : e.type === "status" ? chalk10.yellow(typeLabel) : e.type === "tool_use" ? chalk10.cyan(typeLabel) : typeLabel;
|
|
1967
|
+
printTableRow([chalk10.dim(idx), typeColor, preview]);
|
|
1648
1968
|
}
|
|
1649
1969
|
printTableFooter();
|
|
1650
1970
|
if (hasMore) {
|
|
@@ -1656,8 +1976,8 @@ function parseMemoryStores(value) {
|
|
|
1656
1976
|
}
|
|
1657
1977
|
|
|
1658
1978
|
// src/commands/state.ts
|
|
1659
|
-
import { importResource, parseStateAddress, UserError as
|
|
1660
|
-
import
|
|
1979
|
+
import { importResource, parseStateAddress, UserError as UserError11 } from "@openagentpack/sdk";
|
|
1980
|
+
import chalk11 from "chalk";
|
|
1661
1981
|
async function stateListCommand(options) {
|
|
1662
1982
|
const ctx = await buildCliRuntime(options.file);
|
|
1663
1983
|
const resources = ctx.state.listResources();
|
|
@@ -1672,7 +1992,7 @@ async function stateListCommand(options) {
|
|
|
1672
1992
|
const name = r.address.name.padEnd(20);
|
|
1673
1993
|
const provider = r.address.provider.padEnd(10);
|
|
1674
1994
|
const id = (r.remote_id ?? "(local)").slice(0, 30);
|
|
1675
|
-
console.log(` ${type} ${name}${provider} ${
|
|
1995
|
+
console.log(` ${type} ${name}${provider} ${chalk11.dim(id)}`);
|
|
1676
1996
|
}
|
|
1677
1997
|
printTableFooter();
|
|
1678
1998
|
}
|
|
@@ -1680,14 +2000,14 @@ async function stateShowCommand(address, options) {
|
|
|
1680
2000
|
const ctx = await buildCliRuntime(options.file);
|
|
1681
2001
|
const parsed = parseStateAddress(address, { requireProvider: false });
|
|
1682
2002
|
const found = ctx.state.findResource(parsed);
|
|
1683
|
-
if (!found) throw new
|
|
2003
|
+
if (!found) throw new UserError11(`Resource not found: ${address}`);
|
|
1684
2004
|
console.log(JSON.stringify(found, null, 2));
|
|
1685
2005
|
}
|
|
1686
2006
|
async function stateRemoveCommand(address, options) {
|
|
1687
2007
|
const ctx = await buildCliRuntime(options.file);
|
|
1688
2008
|
const parsed = parseStateAddress(address, { requireProvider: false });
|
|
1689
2009
|
const found = ctx.state.findResource(parsed);
|
|
1690
|
-
if (!found) throw new
|
|
2010
|
+
if (!found) throw new UserError11(`Resource not found: ${address}`);
|
|
1691
2011
|
ctx.state.removeResource(found.address);
|
|
1692
2012
|
await ctx.state.save();
|
|
1693
2013
|
log.success(`Removed ${address} from state (remote resource not deleted).`);
|
|
@@ -1708,14 +2028,14 @@ import {
|
|
|
1708
2028
|
resolveSyncProvider,
|
|
1709
2029
|
syncProviderResourcesFromContext,
|
|
1710
2030
|
syncProviderResourcesFromEnv,
|
|
1711
|
-
UserError as
|
|
2031
|
+
UserError as UserError12
|
|
1712
2032
|
} from "@openagentpack/sdk";
|
|
1713
2033
|
import { stringify as stringifyYaml } from "yaml";
|
|
1714
2034
|
var DEFAULT_SYNC_OUTPUT = "agents.synced.yaml";
|
|
1715
2035
|
function ensureSyncOutputWritable(outPath, force) {
|
|
1716
2036
|
if (force) return;
|
|
1717
2037
|
if (fileExistsSync(outPath)) {
|
|
1718
|
-
throw new
|
|
2038
|
+
throw new UserError12(
|
|
1719
2039
|
`Output file '${outPath}' already exists. Use --force to overwrite, or -o/--out to write elsewhere.`
|
|
1720
2040
|
);
|
|
1721
2041
|
}
|
|
@@ -1777,7 +2097,7 @@ async function syncFromConfig(configPath, explicitProvider) {
|
|
|
1777
2097
|
}
|
|
1778
2098
|
async function syncFromEnv(explicitProvider) {
|
|
1779
2099
|
if (!explicitProvider) {
|
|
1780
|
-
throw new
|
|
2100
|
+
throw new UserError12(
|
|
1781
2101
|
"agents sync requires --provider when no config file exists, e.g. `agents sync --provider claude`."
|
|
1782
2102
|
);
|
|
1783
2103
|
}
|
|
@@ -1989,7 +2309,7 @@ async function serializeConfig(config) {
|
|
|
1989
2309
|
|
|
1990
2310
|
// src/commands/validate.ts
|
|
1991
2311
|
import { resolve as resolve4 } from "path";
|
|
1992
|
-
import { resolveProjectConfig as resolveProjectConfig2, UserError as
|
|
2312
|
+
import { resolveProjectConfig as resolveProjectConfig2, UserError as UserError13, validateProjectConfig } from "@openagentpack/sdk";
|
|
1993
2313
|
async function validateCommand(options) {
|
|
1994
2314
|
ensureCredentials();
|
|
1995
2315
|
const configPath = resolve4(options.file);
|
|
@@ -2004,7 +2324,7 @@ async function validateCommand(options) {
|
|
|
2004
2324
|
}
|
|
2005
2325
|
const errorCount = diagnostics.filter((d) => d.severity === "error").length;
|
|
2006
2326
|
if (errorCount > 0) {
|
|
2007
|
-
throw new
|
|
2327
|
+
throw new UserError13(`Validation failed with ${errorCount} error(s).`);
|
|
2008
2328
|
}
|
|
2009
2329
|
log.success("Configuration is valid.");
|
|
2010
2330
|
}
|
|
@@ -2059,8 +2379,21 @@ var program = new Command2().name("agents").version(readCliVersion()).descriptio
|
|
|
2059
2379
|
color: opts.color !== false
|
|
2060
2380
|
});
|
|
2061
2381
|
});
|
|
2062
|
-
program.command("init").description("Create
|
|
2063
|
-
program.command("playground").description("Launch
|
|
2382
|
+
program.command("init").description("Create an agents.yaml template").option("--provider <provider>", "Provider to configure (bailian, claude, qoder, ark, or all)").option("--agent-name <name>", "Name of the first Agent").action((options) => initCommand(options));
|
|
2383
|
+
program.command("playground").description("Launch a Session Preview for an agents.yaml Agent").addOption(configFileOption()).option("--agent <id>", "Agent to preview (required when the project declares multiple Agents)").option("--port <n>", "Port to serve on (default 4848)").option("--no-open", "Do not open a browser automatically").action(withResolvedConfigFile(playgroundCommand));
|
|
2384
|
+
var projectCmd = program.command("project").description("Manage a directory-based Agent project");
|
|
2385
|
+
projectCmd.command("init").description("Create or convert a directory-based Agent project").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectInitCommand);
|
|
2386
|
+
projectCmd.command("validate").description("Validate all directory project source files").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectValidateCommand);
|
|
2387
|
+
projectCmd.command("build").description("Organize project files and generate .openagentpack/build/agents.yaml").option("--project <directory>", "Project directory", ".").option("--dry-run", "Preview Build without changing files").option("-y, --yes", "Skip confirmation prompt").option("--json", "Output as JSON").action(projectBuildCommand);
|
|
2388
|
+
projectCmd.command("publish").description("Publish the current immutable Build to the configured provider").option("--project <directory>", "Project directory", ".").option("-y, --yes", "Skip confirmation prompt").option("--refresh <value>", "Refresh state before planning (true/false)", parseBooleanOption, true).option("--concurrency <n>", "Max independent resources to publish in parallel", parsePositiveInteger).addOption(providerOption("Target provider", { allowAll: true, defaultValue: "all" })).option("--json", "Output as JSON").action(projectPublishCommand);
|
|
2389
|
+
projectCmd.command("workbench").description("Launch the directory project Workbench").option("--project <directory>", "Project directory", ".").option("--port <n>", "Port to serve on (default 4848)").option("--no-open", "Do not open a browser automatically").action(projectWorkbenchCommand);
|
|
2390
|
+
var projectVersionCmd = projectCmd.command("version").description("Manage directory project snapshots");
|
|
2391
|
+
projectVersionCmd.command("enable").description("Enable Publish-time project versions and create a baseline when needed").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectVersionEnableCommand);
|
|
2392
|
+
projectVersionCmd.command("disable").description("Disable Publish-time project versions without removing history").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectVersionDisableCommand);
|
|
2393
|
+
projectVersionCmd.command("status").description("Show directory project version status").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectVersionStatusCommand);
|
|
2394
|
+
projectVersionCmd.command("list").description("List directory project versions").option("--project <directory>", "Project directory", ".").option("--limit <n>", "Maximum versions to return", parsePositiveInteger).option("--cursor <cursor>", "Pagination cursor").option("--json", "Output as JSON").action(projectVersionListCommand);
|
|
2395
|
+
projectVersionCmd.command("preview <version>").description("Preview a historical directory project version").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectVersionPreviewCommand);
|
|
2396
|
+
projectVersionCmd.command("restore <version>").description("Restore a historical version to the project working directory").option("--project <directory>", "Project directory", ".").option("-y, --yes", "Skip confirmation prompt").option("--json", "Output as JSON").action(projectVersionRestoreCommand);
|
|
2064
2397
|
program.command("validate").description("Validate the configuration file (offline)").addOption(configFileOption()).action(withResolvedConfigFile(validateCommand));
|
|
2065
2398
|
program.command("plan").description("Show what changes would be applied").addOption(configFileOption()).addOption(providerOption("Target provider", { allowAll: true, defaultValue: "all" })).option("--refresh <value>", "Refresh state from remote before planning (true/false)", parseBooleanOption, true).option("--refresh-only", "Refresh state and show drift without planning remote mutations").option("--json", "Output as JSON").action(withResolvedConfigFile(planCommand));
|
|
2066
2399
|
program.command("apply").description("Apply the planned changes to create/update/delete resources").addOption(configFileOption()).option("-y, --yes", "Skip confirmation prompt").option("--refresh <value>", "Refresh state from remote before planning (true/false)", parseBooleanOption, true).option("--refresh-only", "Refresh state without mutating remote resources").option(
|
package/dist/src/program.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openagentpack/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Open Agent Pack — Declaratively manage AI agent infrastructure",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@openagentpack/playground": "0.
|
|
52
|
+
"@openagentpack/playground": "0.7.0",
|
|
53
53
|
"@types/bun": "^1.3.14",
|
|
54
54
|
"typescript": "^6.0.3"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@openagentpack/sdk": "0.
|
|
57
|
+
"@openagentpack/sdk": "0.7.0",
|
|
58
58
|
"@clack/prompts": "^1.5.1",
|
|
59
59
|
"chalk": "^5.6.2",
|
|
60
60
|
"commander": "^14.0.3",
|