@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 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 plan
19
- agents apply
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
@@ -3,7 +3,7 @@ import {
3
3
  configureLogger,
4
4
  log,
5
5
  program
6
- } from "../chunk-V2QDO63Z.js";
6
+ } from "../chunk-C7XMIB3W.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,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 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: 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 UserError8
1711
+ UserError as UserError10
1392
1712
  } from "@openagentpack/sdk";
1393
1713
  import { sanitizeSessionEvent, sanitizeSessionEvents } from "@openagentpack/sdk/session-events";
1394
- import chalk9 from "chalk";
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 UserError8("Specify agent either positionally or with --agent, not both.");
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: ${chalk9.bold(session.id)}`);
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" ? chalk9.green(statusText) : s.status === "processing" ? chalk9.yellow(statusText) : s.status === "failed" ? chalk9.red(statusText) : chalk9.gray(statusText);
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([chalk9.bold(id), title, chalk9.cyan(agent), status, chalk9.dim(created), duration]);
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: ${chalk9.bold(session.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" ? chalk9.green : chalk9.red;
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(chalk9.cyan(`
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(chalk9.dim(preview));
1851
+ log.plain(chalk10.dim(preview));
1532
1852
  } else if (event.type === "status") {
1533
1853
  if (event.status === "running") {
1534
- log.plain(chalk9.yellow("\n[session running]"));
1854
+ log.plain(chalk10.yellow("\n[session running]"));
1535
1855
  }
1536
1856
  } else if (event.type === "error") {
1537
- log.plain(chalk9.red(`
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 UserError8("Specify agent either positionally or with --agent, not both.");
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: ${chalk9.bold(session.id)}`);
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 = chalk9.dim("(thinking)");
1964
+ else if (e.type === "thinking") preview = chalk10.dim("(thinking)");
1645
1965
  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]);
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 UserError9 } from "@openagentpack/sdk";
1660
- import chalk10 from "chalk";
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} ${chalk10.dim(id)}`);
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 UserError9(`Resource not found: ${address}`);
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 UserError9(`Resource not found: ${address}`);
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 UserError10
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 UserError10(
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 UserError10(
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 UserError11, validateProjectConfig } from "@openagentpack/sdk";
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 UserError11(`Validation failed with ${errorCount} error(s).`);
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 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);
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(
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  program
3
- } from "../chunk-V2QDO63Z.js";
3
+ } from "../chunk-C7XMIB3W.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.4.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.4.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.4.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",