@openagentpack/cli 0.5.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,14 +13,22 @@ The package installs the `agents` command.
13
13
  ## Quick start
14
14
 
15
15
  ```sh
16
- agents init
17
- agents validate
18
- agents plan
19
- agents apply
16
+ agents project init
17
+ cd managed-agent
18
+ agents project validate
19
+ agents project build --dry-run
20
+ agents project build
21
+ agents project publish --yes
20
22
  ```
21
23
 
24
+ 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`.
25
+
26
+ 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.
27
+
22
28
  Use `agents <command> --help` for command-specific options.
23
29
 
30
+ Init defaults to a new `managed-agent/` subdirectory under the current working directory. Use `agents project init --project .` to initialize in place or convert an existing `agents.yaml`. Other project commands continue to default to the current working directory.
31
+
24
32
  ## Documentation
25
33
 
26
34
  - [Project README](https://github.com/modelstudioai/OpenAgentPack#readme)
@@ -3,7 +3,7 @@ import {
3
3
  configureLogger,
4
4
  log,
5
5
  program
6
- } from "../chunk-V2QDO63Z.js";
6
+ } from "../chunk-XN4Y3BFX.js";
7
7
 
8
8
  // bin/agents.ts
9
9
  import { UserError } from "@openagentpack/sdk";
@@ -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.`, { output: process.stderr });
365
- throw new UserError2("Apply failed.");
366
- } else {
367
- p2.log.success(`Apply complete! ${succeeded} actions executed successfully.`, { output: process.stderr });
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 GITIGNORE_ADDITIONS = `
651
- # agents
652
- agents.state.json
653
- .env
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
- let providerBlock;
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: ` model: qwen3.7-max`,
679
- claude: ` model: claude-sonnet-4-6`,
680
- qoder: ` model: ultimate`,
681
- ark: ` model: doubao-seed-2-1-pro-260628`
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 = opts.provider === "all" ? ` model:
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[opts.provider];
688
- const toolBlock = opts.provider === "bailian" ? "[bash, read, glob, grep]" : "[read, glob, grep, web_search, web_fetch]";
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: ${opts.provider === "all" ? "all" : opts.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
- ${opts.agentName}:
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
- async function initCommand() {
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 p4.group(
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
- if (await fileExists(gitignorePath)) {
757
- const content2 = await readFile(gitignorePath, "utf8");
758
- if (!content2.includes("agents.state.json")) {
759
- await writeFile(gitignorePath, content2 + GITIGNORE_ADDITIONS, "utf8");
760
- p4.log.success("Updated .gitignore", { output: process.stderr });
761
- }
762
- } else {
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 .gitignore", { output: process.stderr });
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 UserError5,
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 UserError4 } from "@openagentpack/sdk";
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 UserError4(
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 UserError4(`Conflicting config files supplied: ${rootFile} and ${localFile}. Use only one --file value.`);
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 UserError5("Select a provider with --provider when multiple providers are configured.");
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 UserError5("Use either --content or --content-file, not both.");
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 UserError5("Memory content is required; use --content or --content-file.");
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 UserError5("Batch input must be a JSON array of {path, content} objects.");
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 UserError5("Every batch item must contain string path and content fields.");
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 UserError6 } from "@openagentpack/sdk";
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 UserError6(
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 [group2, count] of Object.entries(result.added)) {
1017
- addedParts.push(`${count} ${group2}`);
1021
+ for (const [group, count] of Object.entries(result.added)) {
1022
+ addedParts.push(`${count} ${group}`);
1018
1023
  }
1019
1024
  const skippedParts = [];
1020
- for (const [group2, count] of Object.entries(result.skipped)) {
1021
- skippedParts.push(`${count} ${group2}`);
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 UserError7 } from "@openagentpack/sdk";
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 UserError7("Plan contains errors.");
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 UserError7("Plan contains errors.");
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
- if (res.ok) {
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 { version: body.playground.version ?? "unknown", pid: body.playground.pid };
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 playgroundCommand(options) {
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
- if (options.provider && !SUPPORTED_PLAYGROUND_PROVIDERS.has(options.provider)) {
1333
- const supported = [...SUPPORTED_PLAYGROUND_PROVIDERS].join(", ");
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 url2 = `http://localhost:${port}`;
1341
- log.success(`Playground v${version} already running at ${url2} (pid ${existing.pid})`);
1342
- if (options.open !== false) openBrowser(url2);
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 = { ...process.env, PORT: String(port) };
1354
- if (options.provider) {
1355
- env.AGENTS_PROVIDER = options.provider;
1356
- env.AGENTS_CLI_PROVIDER = options.provider;
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,264 @@ 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 url = `http://localhost:${readyPort}`;
1375
- log.success(`Playground ready at ${url}`);
1376
- if (options.open !== false) openBrowser(url);
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: options.project ?? "./managed-agent" });
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
+ const result = await commitProjectBuild({
1501
+ projectRoot: preview.project_root,
1502
+ baseRevision: preview.project_revision
1503
+ });
1504
+ if (options.json) return writeJson(result.manifest);
1505
+ log.success(`Build created at .openagentpack/build/agents.yaml (${result.manifest.yaml_hash.slice(0, 12)}).`);
1506
+ }
1507
+ async function projectPublishCommand(options) {
1508
+ ensureCredentials();
1509
+ const preview = await planProjectPublish(projectRoot(options), {
1510
+ provider: options.provider,
1511
+ refresh: options.refresh,
1512
+ onFeedback: options.json ? void 0 : renderRuntimeFeedback,
1513
+ quiet: options.json
1514
+ });
1515
+ if (!options.json) renderDiagnostics(preview.planned.plan.diagnostics);
1516
+ if (preview.planned.plan.diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
1517
+ throw new UserError9("Cannot Publish: resolve the plan errors first.");
1518
+ }
1519
+ const actions = preview.planned.plan.actions.filter((action) => action.action !== "no-op");
1520
+ if (!options.json) {
1521
+ for (const action of actions) {
1522
+ const color = action.action === "create" ? chalk9.green : action.action === "update" ? chalk9.yellow : chalk9.red;
1523
+ console.log(
1524
+ color(
1525
+ `${action.action === "create" ? "+" : action.action === "update" ? "~" : "-"} ${formatResourceLabel(action.address)}`
1526
+ )
1527
+ );
1528
+ }
1529
+ }
1530
+ if (!options.yes) {
1531
+ const confirmed = await prompts.confirm({
1532
+ message: actions.length === 0 ? "Publish this Build and record its version?" : `Publish ${actions.length} remote change(s)?`,
1533
+ output: process.stderr
1534
+ });
1535
+ if (prompts.isCancel(confirmed) || !confirmed) {
1536
+ prompts.cancel("Publish cancelled. No remote resources were changed.", { output: process.stderr });
1537
+ return;
1538
+ }
1539
+ }
1540
+ const result = await executeProjectPublish({
1541
+ projectRoot: preview.project_root,
1542
+ expectedProjectRevision: preview.project_revision,
1543
+ expectedYamlHash: preview.build_manifest.yaml_hash,
1544
+ expectedPlanFingerprint: preview.plan_fingerprint,
1545
+ provider: options.provider,
1546
+ refresh: options.refresh,
1547
+ concurrency: options.concurrency,
1548
+ policy: "force",
1549
+ onFeedback: options.json ? void 0 : renderRuntimeFeedback
1550
+ });
1551
+ if (options.json) return writeJson(result);
1552
+ const successful = result.execution?.results.filter((entry) => entry.status === "success").length ?? 0;
1553
+ log.success(`Publish complete. ${successful} remote action(s) succeeded.`);
1554
+ if (result.version) log.success(`Created project version ${result.version.short_version}.`);
1555
+ if (result.working_tree_changed)
1556
+ log.warn("Project files changed during Publish. The published Build is preserved, but a new Build is required.");
1557
+ }
1558
+ async function projectWorkbenchCommand(options) {
1559
+ await workbenchCommand({ project: projectRoot(options), port: options.port, open: options.open });
1560
+ }
1561
+ async function projectVersionEnableCommand(options) {
1562
+ const result = await createDirectoryWorkspaceVersionService(projectRoot(options)).enable(
1563
+ "Initialize project versions"
1564
+ );
1565
+ if (options.json) return writeJson(result);
1566
+ console.log(
1567
+ result.version ? `Created baseline ${result.version.short_version}.` : "Current project source is already versioned."
1568
+ );
1569
+ console.log("Project versioning is enabled.");
1570
+ }
1571
+ async function projectVersionDisableCommand(options) {
1572
+ const result = await createDirectoryWorkspaceVersionService(projectRoot(options)).disable();
1573
+ if (options.json) return writeJson(result);
1574
+ console.log("Project versioning is disabled. Existing versions were kept.");
1575
+ }
1576
+ async function projectVersionStatusCommand(options) {
1577
+ const status = await createDirectoryWorkspaceVersionService(projectRoot(options)).status();
1578
+ if (options.json) return writeJson(status);
1579
+ console.log(`Versioning: ${status.enabled ? "enabled" : "disabled"}`);
1580
+ console.log(`Head: ${status.head_version ?? "none"}`);
1581
+ console.log(`Source: ${status.source_status}`);
1582
+ console.log(`Store: ${status.store_root}`);
1583
+ for (const blocker of [.../* @__PURE__ */ new Set([...status.write_blockers, ...status.restore_blockers])])
1584
+ console.log(chalk9.yellow(`Blocker: ${blocker}`));
1585
+ }
1586
+ async function projectVersionListCommand(options) {
1587
+ const page = await createDirectoryWorkspaceVersionService(projectRoot(options)).listVersions({
1588
+ limit: options.limit,
1589
+ cursor: options.cursor
1590
+ });
1591
+ if (options.json) return writeJson(page);
1592
+ for (const version of page.versions)
1593
+ console.log(
1594
+ `${chalk9.yellow(version.short_version)} ${version.created_at} ${version.message} ${chalk9.dim(`(${version.created_by})`)}`
1595
+ );
1596
+ if (page.versions.length === 0) console.log("No project versions exist.");
1597
+ if (page.next_cursor) console.log(chalk9.dim(`Next cursor: ${page.next_cursor}`));
1598
+ }
1599
+ async function projectVersionPreviewCommand(version, options) {
1600
+ const preview = await createDirectoryWorkspaceVersionService(projectRoot(options)).previewVersion(version);
1601
+ if (options.json) return writeJson(preview);
1602
+ renderDirectoryVersionPreview(preview);
1603
+ }
1604
+ async function projectVersionRestoreCommand(version, options) {
1605
+ const service = createDirectoryWorkspaceVersionService(projectRoot(options));
1606
+ const preview = await service.previewVersion(version);
1607
+ if (!options.json) renderDirectoryVersionPreview(preview);
1608
+ if (!preview.can_restore)
1609
+ throw new UserError9(preview.blockers[0] ?? preview.diagnostics[0]?.message ?? "Version cannot be restored.");
1610
+ if (!options.yes) {
1611
+ const confirmed = await prompts.confirm({
1612
+ message: "Restore this version to the project working directory?",
1613
+ output: process.stderr
1614
+ });
1615
+ if (prompts.isCancel(confirmed) || !confirmed) return;
1616
+ }
1617
+ const restored = await service.restoreVersion(version, {
1618
+ headVersion: preview.base_head_version,
1619
+ projectRevision: preview.base_project_revision
1620
+ });
1621
+ if (options.json) return writeJson(restored);
1622
+ log.success(
1623
+ `Restored ${version.slice(0, 12)} to the working directory. Version head and remote State were not changed.`
1624
+ );
1625
+ }
1626
+ function renderDirectoryVersionPreview(preview) {
1627
+ console.log(chalk9.bold(`Version ${preview.version_id}`));
1628
+ for (const change of preview.changes) {
1629
+ const color = change.change === "create" ? chalk9.green : change.change === "delete" ? chalk9.red : chalk9.yellow;
1630
+ console.log(color(`${change.change} ${change.path}${change.binary ? " (binary)" : ""}`));
1631
+ if (!change.binary) renderSimpleDiff(change.before ?? "", change.after ?? "");
1632
+ }
1633
+ renderDiagnostics(preview.diagnostics);
1634
+ for (const blocker of preview.blockers) console.log(chalk9.yellow(`Blocker: ${blocker}`));
1635
+ console.log(`Can restore: ${preview.can_restore ? "yes" : "no"}`);
1636
+ }
1637
+ async function loadBuildSourcePreview(projectRoot2) {
1638
+ try {
1639
+ const service = createDirectoryWorkspaceVersionService(projectRoot2);
1640
+ const status = await service.status();
1641
+ if (!status.head_version) return { baselineVersion: null, projectRevision: status.project_revision, changes: [] };
1642
+ const preview = await service.previewVersion(status.head_version);
1643
+ return {
1644
+ baselineVersion: status.head_version,
1645
+ projectRevision: preview.base_project_revision,
1646
+ changes: preview.changes
1647
+ };
1648
+ } catch (error) {
1649
+ return {
1650
+ baselineVersion: null,
1651
+ changes: [],
1652
+ error: error instanceof Error ? error.message : String(error)
1653
+ };
1654
+ }
1655
+ }
1656
+ function renderBuildSourcePreview(preview) {
1657
+ console.log(chalk9.bold("Project source changes"));
1658
+ if (preview.error) {
1659
+ console.log(chalk9.yellow(` Source Diff unavailable: ${preview.error}`));
1660
+ return;
1661
+ }
1662
+ if (!preview.baselineVersion) {
1663
+ console.log(chalk9.dim(" Source Diff unavailable: no project version baseline exists."));
1664
+ return;
1665
+ }
1666
+ console.log(chalk9.dim(` baseline ${preview.baselineVersion.slice(0, 12)} -> working tree`));
1667
+ if (preview.changes.length === 0) {
1668
+ console.log(chalk9.dim(" no source changes"));
1669
+ return;
1670
+ }
1671
+ for (const change of preview.changes) {
1672
+ const displayedChange = change.change === "create" ? "delete" : change.change === "delete" ? "create" : "update";
1673
+ const color = displayedChange === "create" ? chalk9.green : displayedChange === "delete" ? chalk9.red : chalk9.yellow;
1674
+ console.log(color(`${displayedChange} ${change.path}${change.binary ? " (binary)" : ""}`));
1675
+ if (!change.binary) renderSimpleDiff(change.after ?? "", change.before ?? "");
1676
+ }
1677
+ }
1678
+ function renderSimpleDiff(before, after) {
1679
+ if (before === after) {
1680
+ console.log(chalk9.dim(" no content changes"));
1681
+ return;
1682
+ }
1683
+ const beforeLines = before.split("\n");
1684
+ const afterLines = after.split("\n");
1685
+ for (const line of beforeLines) if (!afterLines.includes(line)) console.log(chalk9.red(`-${line}`));
1686
+ for (const line of afterLines) if (!beforeLines.includes(line)) console.log(chalk9.green(`+${line}`));
1377
1687
  }
1378
1688
 
1379
1689
  // src/commands/session.ts
@@ -1388,10 +1698,10 @@ import {
1388
1698
  sendSessionMessageStreaming,
1389
1699
  startSessionRun,
1390
1700
  startSessionRunPolling,
1391
- UserError as UserError8
1701
+ UserError as UserError10
1392
1702
  } from "@openagentpack/sdk";
1393
1703
  import { sanitizeSessionEvent, sanitizeSessionEvents } from "@openagentpack/sdk/session-events";
1394
- import chalk9 from "chalk";
1704
+ import chalk10 from "chalk";
1395
1705
  function formatTimestamp(iso) {
1396
1706
  const d = new Date(iso);
1397
1707
  if (Number.isNaN(d.getTime())) return iso;
@@ -1413,7 +1723,7 @@ async function sessionCreateCommand(agentNameOrOptions, maybeOptions) {
1413
1723
  const options = maybeOptions ?? agentNameOrOptions;
1414
1724
  const positionalAgent = typeof agentNameOrOptions === "string" ? agentNameOrOptions : void 0;
1415
1725
  if (positionalAgent && options.agent && positionalAgent !== options.agent) {
1416
- throw new UserError8("Specify agent either positionally or with --agent, not both.");
1726
+ throw new UserError10("Specify agent either positionally or with --agent, not both.");
1417
1727
  }
1418
1728
  const ctx = await buildCliRuntime(options.file);
1419
1729
  const run = await createSessionForAgent(ctx, {
@@ -1429,7 +1739,7 @@ async function sessionCreateCommand(agentNameOrOptions, maybeOptions) {
1429
1739
  title: options.title
1430
1740
  });
1431
1741
  const { agentName, session } = run;
1432
- log.success(`Session created: ${chalk9.bold(session.id)}`);
1742
+ log.success(`Session created: ${chalk10.bold(session.id)}`);
1433
1743
  console.log(` Agent: ${agentName}`);
1434
1744
  console.log(` Environment: ${session.environment_id}`);
1435
1745
  if (session.tunnel_id) console.log(` Tunnel: ${session.tunnel_id}`);
@@ -1476,10 +1786,10 @@ async function sessionListCommand(options) {
1476
1786
  const title = (s.title ?? "").slice(0, 18).padEnd(20);
1477
1787
  const agent = (agentNameMap.get(s.id) ?? s.agent_id.slice(0, 12)).padEnd(14);
1478
1788
  const statusText = s.status.padEnd(12);
1479
- const status = s.status === "idle" ? chalk9.green(statusText) : s.status === "processing" ? chalk9.yellow(statusText) : s.status === "failed" ? chalk9.red(statusText) : chalk9.gray(statusText);
1789
+ const status = s.status === "idle" ? chalk10.green(statusText) : s.status === "processing" ? chalk10.yellow(statusText) : s.status === "failed" ? chalk10.red(statusText) : chalk10.gray(statusText);
1480
1790
  const created = formatTimestamp(s.created_at).padEnd(20);
1481
1791
  const duration = formatDuration(s.created_at, s.status === "idle" ? s.updated_at : void 0);
1482
- printTableRow([chalk9.bold(id), title, chalk9.cyan(agent), status, chalk9.dim(created), duration]);
1792
+ printTableRow([chalk10.bold(id), title, chalk10.cyan(agent), status, chalk10.dim(created), duration]);
1483
1793
  }
1484
1794
  printTableFooter();
1485
1795
  if (hasMore) {
@@ -1489,7 +1799,7 @@ async function sessionListCommand(options) {
1489
1799
  async function sessionGetCommand(sessionId, options) {
1490
1800
  const ctx = await buildCliRuntime(options.file);
1491
1801
  const session = await getSession(ctx, sessionId, options.provider);
1492
- console.log(` ID: ${chalk9.bold(session.id)}`);
1802
+ console.log(` ID: ${chalk10.bold(session.id)}`);
1493
1803
  console.log(` Agent: ${session.agent_id}`);
1494
1804
  console.log(` Environment: ${session.environment_id}`);
1495
1805
  if (session.tunnel_id) console.log(` Tunnel: ${session.tunnel_id}`);
@@ -1510,7 +1820,7 @@ function shouldRenderLiveEvent(event) {
1510
1820
  }
1511
1821
  function renderTerminalStatus(status, json) {
1512
1822
  if (json) return;
1513
- const color = status === "idle" || status === "completed" ? chalk9.green : chalk9.red;
1823
+ const color = status === "idle" || status === "completed" ? chalk10.green : chalk10.red;
1514
1824
  log.plain(color(`
1515
1825
  [session ${status}]`));
1516
1826
  }
@@ -1524,17 +1834,17 @@ function renderEvent(event) {
1524
1834
  if (event.type === "message" && event.content) {
1525
1835
  process.stdout.write(event.content);
1526
1836
  } else if (event.type === "tool_use") {
1527
- log.plain(chalk9.cyan(`
1837
+ log.plain(chalk10.cyan(`
1528
1838
  [tool] ${event.tool_name}`));
1529
1839
  } else if (event.type === "tool_result" && event.content) {
1530
1840
  const preview = event.content.length > 200 ? `${event.content.slice(0, 200)}...` : event.content;
1531
- log.plain(chalk9.dim(preview));
1841
+ log.plain(chalk10.dim(preview));
1532
1842
  } else if (event.type === "status") {
1533
1843
  if (event.status === "running") {
1534
- log.plain(chalk9.yellow("\n[session running]"));
1844
+ log.plain(chalk10.yellow("\n[session running]"));
1535
1845
  }
1536
1846
  } else if (event.type === "error") {
1537
- log.plain(chalk9.red(`
1847
+ log.plain(chalk10.red(`
1538
1848
  [error] ${event.content ?? "unknown error"}`));
1539
1849
  }
1540
1850
  }
@@ -1570,7 +1880,7 @@ async function sessionRunCommand(promptOrAgent, promptOrOptions, maybeOptions) {
1570
1880
  const prompt = hasPositionalAgent ? promptOrOptions : promptOrAgent;
1571
1881
  const options = hasPositionalAgent ? maybeOptions : promptOrOptions ?? maybeOptions;
1572
1882
  if (positionalAgent && options.agent && positionalAgent !== options.agent) {
1573
- throw new UserError8("Specify agent either positionally or with --agent, not both.");
1883
+ throw new UserError10("Specify agent either positionally or with --agent, not both.");
1574
1884
  }
1575
1885
  const runOptions = {
1576
1886
  agent: positionalAgent ?? options.agent,
@@ -1589,7 +1899,7 @@ async function sessionRunCommand(promptOrAgent, promptOrOptions, maybeOptions) {
1589
1899
  const run = stream ? await startSessionRun(ctx, prompt, runOptions) : await startSessionRunPolling(ctx, prompt, runOptions);
1590
1900
  const session = run.session;
1591
1901
  if (!options.json) {
1592
- log.success(`Session created: ${chalk9.bold(session.id)}`);
1902
+ log.success(`Session created: ${chalk10.bold(session.id)}`);
1593
1903
  }
1594
1904
  if (stream) {
1595
1905
  await streamAndRender(run.events, !!options.json);
@@ -1641,10 +1951,10 @@ async function sessionEventsCommand(sessionId, options) {
1641
1951
  else if (e.type === "tool_result") preview = (e.content ?? "").slice(0, 60);
1642
1952
  else if (e.type === "status") preview = `${e.status ?? ""}${e.stop_reason ? ` (${e.stop_reason})` : ""}`;
1643
1953
  else if (e.type === "error") preview = (e.content ?? "").slice(0, 60);
1644
- else if (e.type === "thinking") preview = chalk9.dim("(thinking)");
1954
+ else if (e.type === "thinking") preview = chalk10.dim("(thinking)");
1645
1955
  else preview = e.raw_type;
1646
- const typeColor = e.type === "error" ? chalk9.red(typeLabel) : e.type === "status" ? chalk9.yellow(typeLabel) : e.type === "tool_use" ? chalk9.cyan(typeLabel) : typeLabel;
1647
- printTableRow([chalk9.dim(idx), typeColor, preview]);
1956
+ const typeColor = e.type === "error" ? chalk10.red(typeLabel) : e.type === "status" ? chalk10.yellow(typeLabel) : e.type === "tool_use" ? chalk10.cyan(typeLabel) : typeLabel;
1957
+ printTableRow([chalk10.dim(idx), typeColor, preview]);
1648
1958
  }
1649
1959
  printTableFooter();
1650
1960
  if (hasMore) {
@@ -1656,8 +1966,8 @@ function parseMemoryStores(value) {
1656
1966
  }
1657
1967
 
1658
1968
  // src/commands/state.ts
1659
- import { importResource, parseStateAddress, UserError as UserError9 } from "@openagentpack/sdk";
1660
- import chalk10 from "chalk";
1969
+ import { importResource, parseStateAddress, UserError as UserError11 } from "@openagentpack/sdk";
1970
+ import chalk11 from "chalk";
1661
1971
  async function stateListCommand(options) {
1662
1972
  const ctx = await buildCliRuntime(options.file);
1663
1973
  const resources = ctx.state.listResources();
@@ -1672,7 +1982,7 @@ async function stateListCommand(options) {
1672
1982
  const name = r.address.name.padEnd(20);
1673
1983
  const provider = r.address.provider.padEnd(10);
1674
1984
  const id = (r.remote_id ?? "(local)").slice(0, 30);
1675
- console.log(` ${type} ${name}${provider} ${chalk10.dim(id)}`);
1985
+ console.log(` ${type} ${name}${provider} ${chalk11.dim(id)}`);
1676
1986
  }
1677
1987
  printTableFooter();
1678
1988
  }
@@ -1680,14 +1990,14 @@ async function stateShowCommand(address, options) {
1680
1990
  const ctx = await buildCliRuntime(options.file);
1681
1991
  const parsed = parseStateAddress(address, { requireProvider: false });
1682
1992
  const found = ctx.state.findResource(parsed);
1683
- if (!found) throw new UserError9(`Resource not found: ${address}`);
1993
+ if (!found) throw new UserError11(`Resource not found: ${address}`);
1684
1994
  console.log(JSON.stringify(found, null, 2));
1685
1995
  }
1686
1996
  async function stateRemoveCommand(address, options) {
1687
1997
  const ctx = await buildCliRuntime(options.file);
1688
1998
  const parsed = parseStateAddress(address, { requireProvider: false });
1689
1999
  const found = ctx.state.findResource(parsed);
1690
- if (!found) throw new UserError9(`Resource not found: ${address}`);
2000
+ if (!found) throw new UserError11(`Resource not found: ${address}`);
1691
2001
  ctx.state.removeResource(found.address);
1692
2002
  await ctx.state.save();
1693
2003
  log.success(`Removed ${address} from state (remote resource not deleted).`);
@@ -1708,14 +2018,14 @@ import {
1708
2018
  resolveSyncProvider,
1709
2019
  syncProviderResourcesFromContext,
1710
2020
  syncProviderResourcesFromEnv,
1711
- UserError as UserError10
2021
+ UserError as UserError12
1712
2022
  } from "@openagentpack/sdk";
1713
2023
  import { stringify as stringifyYaml } from "yaml";
1714
2024
  var DEFAULT_SYNC_OUTPUT = "agents.synced.yaml";
1715
2025
  function ensureSyncOutputWritable(outPath, force) {
1716
2026
  if (force) return;
1717
2027
  if (fileExistsSync(outPath)) {
1718
- throw new UserError10(
2028
+ throw new UserError12(
1719
2029
  `Output file '${outPath}' already exists. Use --force to overwrite, or -o/--out to write elsewhere.`
1720
2030
  );
1721
2031
  }
@@ -1777,7 +2087,7 @@ async function syncFromConfig(configPath, explicitProvider) {
1777
2087
  }
1778
2088
  async function syncFromEnv(explicitProvider) {
1779
2089
  if (!explicitProvider) {
1780
- throw new UserError10(
2090
+ throw new UserError12(
1781
2091
  "agents sync requires --provider when no config file exists, e.g. `agents sync --provider claude`."
1782
2092
  );
1783
2093
  }
@@ -1989,7 +2299,7 @@ async function serializeConfig(config) {
1989
2299
 
1990
2300
  // src/commands/validate.ts
1991
2301
  import { resolve as resolve4 } from "path";
1992
- import { resolveProjectConfig as resolveProjectConfig2, UserError as UserError11, validateProjectConfig } from "@openagentpack/sdk";
2302
+ import { resolveProjectConfig as resolveProjectConfig2, UserError as UserError13, validateProjectConfig } from "@openagentpack/sdk";
1993
2303
  async function validateCommand(options) {
1994
2304
  ensureCredentials();
1995
2305
  const configPath = resolve4(options.file);
@@ -2004,7 +2314,7 @@ async function validateCommand(options) {
2004
2314
  }
2005
2315
  const errorCount = diagnostics.filter((d) => d.severity === "error").length;
2006
2316
  if (errorCount > 0) {
2007
- throw new UserError11(`Validation failed with ${errorCount} error(s).`);
2317
+ throw new UserError13(`Validation failed with ${errorCount} error(s).`);
2008
2318
  }
2009
2319
  log.success("Configuration is valid.");
2010
2320
  }
@@ -2059,8 +2369,25 @@ var program = new Command2().name("agents").version(readCliVersion()).descriptio
2059
2369
  color: opts.color !== false
2060
2370
  });
2061
2371
  });
2062
- program.command("init").description("Create a new agents.yaml template").action(initCommand);
2063
- program.command("playground").description("Launch the local web UI (fetches @openagentpack/playground on demand) and open it in a browser").option("--port <n>", "Port to serve on (default 4848)").addOption(providerOption("Provider the UI targets")).option("--no-open", "Do not open a browser automatically").action(playgroundCommand);
2372
+ 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));
2373
+ 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));
2374
+ var projectCmd = program.command("project").description("Manage a directory-based Agent project");
2375
+ projectCmd.command("init").description("Create or convert a directory-based Agent project").option(
2376
+ "--project <directory>",
2377
+ "Project directory (use . to initialize or convert agents.yaml in place)",
2378
+ "./managed-agent"
2379
+ ).option("--json", "Output as JSON").action(projectInitCommand);
2380
+ projectCmd.command("validate").description("Validate all directory project source files").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectValidateCommand);
2381
+ 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("--json", "Output as JSON").action(projectBuildCommand);
2382
+ 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);
2383
+ 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);
2384
+ var projectVersionCmd = projectCmd.command("version").description("Manage directory project snapshots");
2385
+ 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);
2386
+ projectVersionCmd.command("disable").description("Disable Publish-time project versions without removing history").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectVersionDisableCommand);
2387
+ projectVersionCmd.command("status").description("Show directory project version status").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectVersionStatusCommand);
2388
+ 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);
2389
+ projectVersionCmd.command("preview <version>").description("Preview a historical directory project version").option("--project <directory>", "Project directory", ".").option("--json", "Output as JSON").action(projectVersionPreviewCommand);
2390
+ 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
2391
  program.command("validate").description("Validate the configuration file (offline)").addOption(configFileOption()).action(withResolvedConfigFile(validateCommand));
2065
2392
  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
2393
  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(
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  program
3
- } from "../chunk-V2QDO63Z.js";
3
+ } from "../chunk-XN4Y3BFX.js";
4
4
  export {
5
5
  program
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagentpack/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.1",
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.5.0",
52
+ "@openagentpack/playground": "0.7.1",
53
53
  "@types/bun": "^1.3.14",
54
54
  "typescript": "^6.0.3"
55
55
  },
56
56
  "dependencies": {
57
- "@openagentpack/sdk": "0.5.0",
57
+ "@openagentpack/sdk": "0.7.1",
58
58
  "@clack/prompts": "^1.5.1",
59
59
  "chalk": "^5.6.2",
60
60
  "commander": "^14.0.3",