@asteroidai/cli 1.9.4 → 1.9.12

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/dist/core.js CHANGED
@@ -145,7 +145,8 @@ var COMMANDS = [
145
145
  query: [],
146
146
  binary: false,
147
147
  readOnly: false,
148
- destructive: false
148
+ destructive: false,
149
+ capability: "run_execution"
149
150
  },
150
151
  {
151
152
  command: "context",
@@ -1051,7 +1052,8 @@ var COMMANDS = [
1051
1052
  ],
1052
1053
  files: [],
1053
1054
  credentialOrganization: false
1054
- }
1055
+ },
1056
+ capability: "run_execution"
1055
1057
  },
1056
1058
  {
1057
1059
  command: "execution resume",
@@ -1071,7 +1073,8 @@ var COMMANDS = [
1071
1073
  query: [],
1072
1074
  binary: false,
1073
1075
  readOnly: false,
1074
- destructive: false
1076
+ destructive: false,
1077
+ capability: "run_execution"
1075
1078
  },
1076
1079
  {
1077
1080
  command: "insight get",
@@ -1919,7 +1922,8 @@ var COMMANDS = [
1919
1922
  ],
1920
1923
  files: [],
1921
1924
  credentialOrganization: false
1922
- }
1925
+ },
1926
+ capability: "run_execution"
1923
1927
  },
1924
1928
  {
1925
1929
  command: "schedule delete",
@@ -1945,7 +1949,8 @@ var COMMANDS = [
1945
1949
  query: [],
1946
1950
  binary: false,
1947
1951
  readOnly: false,
1948
- destructive: true
1952
+ destructive: true,
1953
+ capability: "run_execution"
1949
1954
  },
1950
1955
  {
1951
1956
  command: "schedule get",
@@ -2126,7 +2131,8 @@ var COMMANDS = [
2126
2131
  ],
2127
2132
  files: [],
2128
2133
  credentialOrganization: false
2129
- }
2134
+ },
2135
+ capability: "run_execution"
2130
2136
  },
2131
2137
  {
2132
2138
  command: "scheduled-execution cancel",
@@ -2146,7 +2152,8 @@ var COMMANDS = [
2146
2152
  query: [],
2147
2153
  binary: false,
2148
2154
  readOnly: false,
2149
- destructive: false
2155
+ destructive: false,
2156
+ capability: "run_execution"
2150
2157
  },
2151
2158
  {
2152
2159
  command: "scheduled-execution create",
@@ -2214,7 +2221,8 @@ var COMMANDS = [
2214
2221
  ],
2215
2222
  files: [],
2216
2223
  credentialOrganization: false
2217
- }
2224
+ },
2225
+ capability: "run_execution"
2218
2226
  },
2219
2227
  {
2220
2228
  command: "scheduled-execution list",
@@ -2316,7 +2324,8 @@ var COMMANDS = [
2316
2324
  ],
2317
2325
  files: [],
2318
2326
  credentialOrganization: false
2319
- }
2327
+ },
2328
+ capability: "run_execution"
2320
2329
  },
2321
2330
  {
2322
2331
  command: "schema validate",
@@ -3456,7 +3465,8 @@ var COMMANDS = [
3456
3465
  ],
3457
3466
  files: [],
3458
3467
  credentialOrganization: false
3459
- }
3468
+ },
3469
+ capability: "run_execution"
3460
3470
  },
3461
3471
  {
3462
3472
  command: "workflow execute-sync",
@@ -3555,112 +3565,8 @@ var COMMANDS = [
3555
3565
  ],
3556
3566
  files: [],
3557
3567
  credentialOrganization: false
3558
- }
3559
- },
3560
- {
3561
- command: "workflow file list",
3562
- tool: "workflow_files_list",
3563
- title: "List workflow files",
3564
- description: "List the workflow's editable head as a directory of files: the graph rendered as structural files (settings.yaml, per-node instructions.md, scripts) alongside the workflow's own files. Those files are referenced by fileId unless contents=all is set. Pass paths to read named files only, or contents=none for a manifest with no bodies. The returned rev is what you pass as baseRev to write.",
3565
- method: "GET",
3566
- path: "/agents/v2/workflows/{workflowId}/head/files",
3567
- args: [
3568
- {
3569
- name: "workflowId",
3570
- flag: "workflow-id",
3571
- type: "string",
3572
- required: true
3573
- }
3574
- ],
3575
- query: [
3576
- {
3577
- name: "contents",
3578
- flag: "contents",
3579
- type: "string",
3580
- required: false,
3581
- description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
3582
- enum: ["none", "structural", "all"]
3583
- },
3584
- {
3585
- name: "paths",
3586
- flag: "paths",
3587
- type: "string[]",
3588
- required: false,
3589
- description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
3590
- },
3591
- {
3592
- name: "variants",
3593
- flag: "variants",
3594
- type: "string",
3595
- required: false,
3596
- description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
3597
- },
3598
- {
3599
- name: "maxFileBytes",
3600
- flag: "max-file-bytes",
3601
- type: "integer",
3602
- required: false,
3603
- description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
3604
- },
3605
- {
3606
- name: "maxTotalBytes",
3607
- flag: "max-total-bytes",
3608
- type: "integer",
3609
- required: false,
3610
- description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
3611
- }
3612
- ],
3613
- binary: false,
3614
- readOnly: true,
3615
- destructive: false
3616
- },
3617
- {
3618
- command: "workflow file patch",
3619
- tool: "workflow_files_patch",
3620
- title: "Write workflow files",
3621
- description: "Write and delete files on the workflow's editable head in one patch. Structural files re-derive the graph; the workflow's own files go to its file store, you just write paths. Pass the rev from `workflow_files_list` as baseRev; a stale value is rejected with 409, so re-read and retry. Editing a published or already-executed version forks a new one instead of rewriting it. Each write carries `contentBase64`: base64-encode the file content, plain text is rejected with 400.",
3622
- method: "PATCH",
3623
- path: "/agents/v2/workflows/{workflowId}/head/files",
3624
- args: [
3625
- {
3626
- name: "workflowId",
3627
- flag: "workflow-id",
3628
- type: "string",
3629
- required: true
3630
- }
3631
- ],
3632
- query: [],
3633
- binary: false,
3634
- readOnly: false,
3635
- destructive: true,
3636
- body: {
3637
- required: true,
3638
- contentType: "application/json",
3639
- fields: [
3640
- {
3641
- name: "baseRev",
3642
- flag: "base-rev",
3643
- type: "integer",
3644
- required: true,
3645
- description: "The revision this edit is based on. Agent-file-only patches overlay the current head even when this is stale. A structural-file patch is rejected with 409 when it does not match the stored head revision."
3646
- },
3647
- {
3648
- name: "writes",
3649
- flag: "writes",
3650
- type: "json",
3651
- required: true,
3652
- description: "Files to upsert."
3653
- },
3654
- {
3655
- name: "deletes",
3656
- flag: "deletes",
3657
- type: "string[]",
3658
- required: true
3659
- }
3660
- ],
3661
- files: [],
3662
- credentialOrganization: false
3663
- }
3568
+ },
3569
+ capability: "run_execution"
3664
3570
  },
3665
3571
  {
3666
3572
  command: "workflow get",
@@ -3708,7 +3614,8 @@ var COMMANDS = [
3708
3614
  query: [],
3709
3615
  binary: false,
3710
3616
  readOnly: false,
3711
- destructive: false
3617
+ destructive: false,
3618
+ capability: "publish"
3712
3619
  },
3713
3620
  {
3714
3621
  command: "workflow head revert",
@@ -3834,6 +3741,113 @@ var COMMANDS = [
3834
3741
  readOnly: true,
3835
3742
  destructive: false
3836
3743
  },
3744
+ {
3745
+ command: "workflow pull",
3746
+ tool: "workflow_files_list",
3747
+ title: "List workflow files",
3748
+ description: "List the workflow's editable head as a directory of files: the graph rendered as structural files (settings.yaml, per-node instructions.md, scripts) alongside the workflow's own files. Those files are referenced by fileId unless contents=all is set. Pass paths to read named files only, or contents=none for a manifest with no bodies. The returned rev is what you pass as baseRev to write.",
3749
+ method: "GET",
3750
+ path: "/agents/v2/workflows/{workflowId}/head/files",
3751
+ args: [
3752
+ {
3753
+ name: "workflowId",
3754
+ flag: "workflow-id",
3755
+ type: "string",
3756
+ required: true
3757
+ }
3758
+ ],
3759
+ query: [
3760
+ {
3761
+ name: "contents",
3762
+ flag: "contents",
3763
+ type: "string",
3764
+ required: false,
3765
+ description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
3766
+ enum: ["none", "structural", "all"]
3767
+ },
3768
+ {
3769
+ name: "paths",
3770
+ flag: "paths",
3771
+ type: "string[]",
3772
+ required: false,
3773
+ description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
3774
+ },
3775
+ {
3776
+ name: "variants",
3777
+ flag: "variants",
3778
+ type: "string",
3779
+ required: false,
3780
+ description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
3781
+ },
3782
+ {
3783
+ name: "maxFileBytes",
3784
+ flag: "max-file-bytes",
3785
+ type: "integer",
3786
+ required: false,
3787
+ description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
3788
+ },
3789
+ {
3790
+ name: "maxTotalBytes",
3791
+ flag: "max-total-bytes",
3792
+ type: "integer",
3793
+ required: false,
3794
+ description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
3795
+ }
3796
+ ],
3797
+ binary: false,
3798
+ readOnly: true,
3799
+ destructive: false,
3800
+ files: "pull"
3801
+ },
3802
+ {
3803
+ command: "workflow push",
3804
+ tool: "workflow_files_patch",
3805
+ title: "Write workflow files",
3806
+ description: "Write and delete files on the workflow's editable head in one patch. Structural files re-derive the graph; the workflow's own files go to its file store, you just write paths. Pass the rev from `workflow_files_list` as baseRev; a stale value is rejected with 409, so re-read and retry. Editing a published or already-executed version forks a new one instead of rewriting it. Each write carries `contentBase64`: base64-encode the file content, plain text is rejected with 400.",
3807
+ method: "PATCH",
3808
+ path: "/agents/v2/workflows/{workflowId}/head/files",
3809
+ args: [
3810
+ {
3811
+ name: "workflowId",
3812
+ flag: "workflow-id",
3813
+ type: "string",
3814
+ required: true
3815
+ }
3816
+ ],
3817
+ query: [],
3818
+ binary: false,
3819
+ readOnly: false,
3820
+ destructive: true,
3821
+ body: {
3822
+ required: true,
3823
+ contentType: "application/json",
3824
+ fields: [
3825
+ {
3826
+ name: "baseRev",
3827
+ flag: "base-rev",
3828
+ type: "integer",
3829
+ required: true,
3830
+ description: "The revision this edit is based on. Agent-file-only patches overlay the current head even when this is stale. A structural-file patch is rejected with 409 when it does not match the stored head revision."
3831
+ },
3832
+ {
3833
+ name: "writes",
3834
+ flag: "writes",
3835
+ type: "json",
3836
+ required: true,
3837
+ description: "Files to upsert."
3838
+ },
3839
+ {
3840
+ name: "deletes",
3841
+ flag: "deletes",
3842
+ type: "string[]",
3843
+ required: true
3844
+ }
3845
+ ],
3846
+ files: [],
3847
+ credentialOrganization: false
3848
+ },
3849
+ files: "push"
3850
+ },
3837
3851
  {
3838
3852
  command: "workflow rename",
3839
3853
  tool: "workflow_rename",
@@ -4236,15 +4250,16 @@ var COMMANDS = [
4236
4250
  ],
4237
4251
  files: [],
4238
4252
  credentialOrganization: false
4239
- }
4253
+ },
4254
+ capability: "run_execution"
4240
4255
  },
4241
4256
  {
4242
- command: "workflow version file list",
4243
- tool: "workflow_version_files_list",
4244
- title: "List workflow version files",
4245
- description: "List a version's files, like `workflow_files_list` does for the editable head. Versions are immutable, so this is read-only.",
4257
+ command: "workflow version file url",
4258
+ tool: "workflow_version_file_signed_url",
4259
+ title: "Get workflow version file signed URL",
4260
+ description: "Get a short-lived signed URL for downloading one of a workflow version's files directly from storage. Prefer this over a redirect-based download when reading a file's contents.",
4246
4261
  method: "GET",
4247
- path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files",
4262
+ path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files/{fileId}/signed-url",
4248
4263
  args: [
4249
4264
  {
4250
4265
  name: "workflowId",
@@ -4257,93 +4272,30 @@ var COMMANDS = [
4257
4272
  flag: "version-id",
4258
4273
  type: "string",
4259
4274
  required: true
4275
+ },
4276
+ {
4277
+ name: "fileId",
4278
+ flag: "file-id",
4279
+ type: "string",
4280
+ required: true
4260
4281
  }
4261
4282
  ],
4262
- query: [
4283
+ query: [],
4284
+ binary: false,
4285
+ readOnly: true,
4286
+ destructive: false
4287
+ },
4288
+ {
4289
+ command: "workflow version get",
4290
+ tool: "workflow_version_get",
4291
+ title: "Get workflow version",
4292
+ description: "Get a full workflow version by `versionId`, including the complete node graph, rules, and settings.",
4293
+ method: "GET",
4294
+ path: "/agents/v2/workflows/{workflowId}/versions/{versionId}",
4295
+ args: [
4263
4296
  {
4264
- name: "contents",
4265
- flag: "contents",
4266
- type: "string",
4267
- required: false,
4268
- description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
4269
- enum: ["none", "structural", "all"]
4270
- },
4271
- {
4272
- name: "paths",
4273
- flag: "paths",
4274
- type: "string[]",
4275
- required: false,
4276
- description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
4277
- },
4278
- {
4279
- name: "variants",
4280
- flag: "variants",
4281
- type: "string",
4282
- required: false,
4283
- description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
4284
- },
4285
- {
4286
- name: "maxFileBytes",
4287
- flag: "max-file-bytes",
4288
- type: "integer",
4289
- required: false,
4290
- description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
4291
- },
4292
- {
4293
- name: "maxTotalBytes",
4294
- flag: "max-total-bytes",
4295
- type: "integer",
4296
- required: false,
4297
- description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
4298
- }
4299
- ],
4300
- binary: false,
4301
- readOnly: true,
4302
- destructive: false
4303
- },
4304
- {
4305
- command: "workflow version file url",
4306
- tool: "workflow_version_file_signed_url",
4307
- title: "Get workflow version file signed URL",
4308
- description: "Get a short-lived signed URL for downloading one of a workflow version's files directly from storage. Prefer this over a redirect-based download when reading a file's contents.",
4309
- method: "GET",
4310
- path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files/{fileId}/signed-url",
4311
- args: [
4312
- {
4313
- name: "workflowId",
4314
- flag: "workflow-id",
4315
- type: "string",
4316
- required: true
4317
- },
4318
- {
4319
- name: "versionId",
4320
- flag: "version-id",
4321
- type: "string",
4322
- required: true
4323
- },
4324
- {
4325
- name: "fileId",
4326
- flag: "file-id",
4327
- type: "string",
4328
- required: true
4329
- }
4330
- ],
4331
- query: [],
4332
- binary: false,
4333
- readOnly: true,
4334
- destructive: false
4335
- },
4336
- {
4337
- command: "workflow version get",
4338
- tool: "workflow_version_get",
4339
- title: "Get workflow version",
4340
- description: "Get a full workflow version by `versionId`, including the complete node graph, rules, and settings.",
4341
- method: "GET",
4342
- path: "/agents/v2/workflows/{workflowId}/versions/{versionId}",
4343
- args: [
4344
- {
4345
- name: "workflowId",
4346
- flag: "workflow-id",
4297
+ name: "workflowId",
4298
+ flag: "workflow-id",
4347
4299
  type: "string",
4348
4300
  required: true
4349
4301
  },
@@ -4455,7 +4407,72 @@ var COMMANDS = [
4455
4407
  query: [],
4456
4408
  binary: false,
4457
4409
  readOnly: false,
4458
- destructive: false
4410
+ destructive: false,
4411
+ capability: "publish"
4412
+ },
4413
+ {
4414
+ command: "workflow version pull",
4415
+ tool: "workflow_version_files_list",
4416
+ title: "List workflow version files",
4417
+ description: "List a version's files, like `workflow_files_list` does for the editable head. Versions are immutable, so this is read-only.",
4418
+ method: "GET",
4419
+ path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files",
4420
+ args: [
4421
+ {
4422
+ name: "workflowId",
4423
+ flag: "workflow-id",
4424
+ type: "string",
4425
+ required: true
4426
+ },
4427
+ {
4428
+ name: "versionId",
4429
+ flag: "version-id",
4430
+ type: "string",
4431
+ required: true
4432
+ }
4433
+ ],
4434
+ query: [
4435
+ {
4436
+ name: "contents",
4437
+ flag: "contents",
4438
+ type: "string",
4439
+ required: false,
4440
+ description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
4441
+ enum: ["none", "structural", "all"]
4442
+ },
4443
+ {
4444
+ name: "paths",
4445
+ flag: "paths",
4446
+ type: "string[]",
4447
+ required: false,
4448
+ description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
4449
+ },
4450
+ {
4451
+ name: "variants",
4452
+ flag: "variants",
4453
+ type: "string",
4454
+ required: false,
4455
+ description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
4456
+ },
4457
+ {
4458
+ name: "maxFileBytes",
4459
+ flag: "max-file-bytes",
4460
+ type: "integer",
4461
+ required: false,
4462
+ description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
4463
+ },
4464
+ {
4465
+ name: "maxTotalBytes",
4466
+ flag: "max-total-bytes",
4467
+ type: "integer",
4468
+ required: false,
4469
+ description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
4470
+ }
4471
+ ],
4472
+ binary: false,
4473
+ readOnly: true,
4474
+ destructive: false,
4475
+ files: "pull"
4459
4476
  },
4460
4477
  {
4461
4478
  command: "workflow version validate",
@@ -4555,16 +4572,16 @@ function isScalar(value2) {
4555
4572
  return value2 === null || ["string", "number", "boolean"].includes(typeof value2);
4556
4573
  }
4557
4574
  function indent(text2) {
4558
- return text2.split("\n").map((line) => ` ${line}`).join("\n");
4575
+ return text2.split("\n").map((line2) => ` ${line2}`).join("\n");
4559
4576
  }
4560
4577
  function text(value2) {
4561
4578
  return clean(isScalar(value2) ? String(value2) : JSON.stringify(value2));
4562
4579
  }
4563
4580
  function cell(value2, options = {}) {
4564
4581
  if (value2 === null || value2 === void 0) return "";
4565
- const line = text(value2).replace(/\s+/g, " ");
4566
- if (options.full || line.length <= MAX_CELL) return line;
4567
- return `${line.slice(0, MAX_CELL - 1)}\u2026`;
4582
+ const line2 = text(value2).replace(/\s+/g, " ");
4583
+ if (options.full || line2.length <= MAX_CELL) return line2;
4584
+ return `${line2.slice(0, MAX_CELL - 1)}\u2026`;
4568
4585
  }
4569
4586
  function columns(rows, options) {
4570
4587
  const scalarKeys = [];
@@ -4612,8 +4629,8 @@ function table(input, options = {}) {
4612
4629
  const widths = keys.map(
4613
4630
  (key, i) => Math.max(key.length, ...cells.map((r) => r[i].length))
4614
4631
  );
4615
- const line = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
4616
- return [line(keys.map((k) => k.toUpperCase())), ...cells.map(line)].join(
4632
+ const line2 = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
4633
+ return [line2(keys.map((k) => k.toUpperCase())), ...cells.map(line2)].join(
4617
4634
  "\n"
4618
4635
  );
4619
4636
  }
@@ -4727,12 +4744,12 @@ var DOTTED = /^--([a-z0-9-]+\.[a-z0-9.-]+)(?:=(.*))?$/;
4727
4744
  function collect(value2, previous) {
4728
4745
  return [...previous ?? [], value2];
4729
4746
  }
4730
- function optionText(option) {
4731
- const text2 = option.description?.replace(/\s+/g, " ").trim() ?? "";
4747
+ function optionText(option2) {
4748
+ const text2 = option2.description?.replace(/\s+/g, " ").trim() ?? "";
4732
4749
  const parts = [text2 && !/[.!?]$/.test(text2) ? `${text2}.` : text2];
4733
- if (option.enum) parts.push(`One of: ${option.enum.join(", ")}.`);
4734
- if (option.type.endsWith("[]")) parts.push("Repeatable.");
4735
- if (option.required) parts.push("Required.");
4750
+ if (option2.enum) parts.push(`One of: ${option2.enum.join(", ")}.`);
4751
+ if (option2.type.endsWith("[]")) parts.push("Repeatable.");
4752
+ if (option2.required) parts.push("Required.");
4736
4753
  return parts.filter(Boolean).join(" ");
4737
4754
  }
4738
4755
  function splitDotted(argv) {
@@ -4759,32 +4776,44 @@ function group(parent, name, groups, path) {
4759
4776
  return created;
4760
4777
  }
4761
4778
  function leafOptions(leaf, command) {
4762
- for (const option of [...command.query, ...command.body?.fields ?? []]) {
4763
- if (option.type === "boolean") {
4764
- leaf.option(`--${option.flag}`, optionText(option));
4765
- leaf.option(`--no-${option.flag}`, `Set ${option.flag} to false.`);
4779
+ for (const option2 of [...command.query, ...command.body?.fields ?? []]) {
4780
+ if (option2.type === "boolean") {
4781
+ leaf.option(`--${option2.flag}`, optionText(option2));
4782
+ leaf.option(`--no-${option2.flag}`, `Set ${option2.flag} to false.`);
4766
4783
  continue;
4767
4784
  }
4768
- const placeholder = option.enum ? option.enum.join("|") : option.type.replace("[]", "");
4785
+ const placeholder = option2.enum ? option2.enum.join("|") : option2.type.replace("[]", "");
4769
4786
  leaf.option(
4770
- `--${option.flag} <${placeholder}>`,
4771
- optionText(option),
4787
+ `--${option2.flag} <${placeholder}>`,
4788
+ optionText(option2),
4772
4789
  collect
4773
4790
  );
4791
+ if (option2.negatable)
4792
+ leaf.option(`--no-${option2.flag}`, `Skip ${option2.flag}.`);
4774
4793
  }
4775
4794
  for (const file of command.body?.files ?? []) {
4776
4795
  leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
4777
4796
  }
4797
+ if (command.files === "pull")
4798
+ leaf.option(
4799
+ "--out <dir>",
4800
+ "Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead."
4801
+ );
4802
+ if (command.files === "push")
4803
+ leaf.option(
4804
+ "--from <dir>",
4805
+ "The pulled head folder to push. Defaults to the current folder."
4806
+ );
4778
4807
  }
4779
4808
  function toParsed(leaf, positionals, dotted) {
4780
4809
  const globals = leaf.optsWithGlobals();
4781
4810
  const own = leaf.opts();
4782
4811
  const flags = new Map(dotted);
4783
- for (const option of leaf.options) {
4784
- const flag = option.long?.replace(/^--(no-)?/, "");
4785
- const value2 = own[option.attributeName()];
4786
- if (!flag || value2 === void 0 || flags.has(flag)) continue;
4787
- flags.set(flag, Array.isArray(value2) ? value2.map(String) : [String(value2)]);
4812
+ for (const option2 of leaf.options) {
4813
+ const flag2 = option2.long?.replace(/^--(no-)?/, "");
4814
+ const value2 = own[option2.attributeName()];
4815
+ if (!flag2 || value2 === void 0 || flags.has(flag2)) continue;
4816
+ flags.set(flag2, Array.isArray(value2) ? value2.map(String) : [String(value2)]);
4788
4817
  }
4789
4818
  return {
4790
4819
  positionals,
@@ -4797,7 +4826,11 @@ function toParsed(leaf, positionals, dotted) {
4797
4826
  }
4798
4827
  function defineCommands(root, options, dotted = /* @__PURE__ */ new Map(), onInvocation = () => void 0) {
4799
4828
  if (options.description) root.description(options.description);
4800
- if (options.version) root.version(options.version, "-v, --version");
4829
+ if (options.version)
4830
+ root.addHelpText(
4831
+ "after",
4832
+ "\nRun `asteroid --version` to print the CLI version."
4833
+ );
4801
4834
  for (const [flags, text2] of GLOBAL_FLAGS) {
4802
4835
  if (!flags.startsWith("-h")) root.option(flags, text2);
4803
4836
  }
@@ -4859,8 +4892,8 @@ ${notes.join("\n")}`);
4859
4892
  );
4860
4893
  }
4861
4894
  const unknownDotted = [...dotted.keys()].filter(
4862
- (flag) => !(command.body?.fields ?? []).some(
4863
- (f) => flag.startsWith(`${f.flag}.`)
4895
+ (flag2) => !(command.body?.fields ?? []).some(
4896
+ (f) => flag2.startsWith(`${f.flag}.`)
4864
4897
  )
4865
4898
  );
4866
4899
  if (unknownDotted.length > 0) {
@@ -4881,6 +4914,15 @@ async function parseInvocation(argv, options) {
4881
4914
  let stdout = "";
4882
4915
  let stderr = "";
4883
4916
  let result;
4917
+ if (options.version && (argv[0] === "-v" || argv[0] === "--version")) {
4918
+ return {
4919
+ kind: "exit",
4920
+ code: 0,
4921
+ stdout: `${options.version}
4922
+ `,
4923
+ stderr: ""
4924
+ };
4925
+ }
4884
4926
  const { argv: rest, dotted } = splitDotted(argv);
4885
4927
  const root = new Command("asteroid").exitOverride().helpCommand(false).configureOutput({
4886
4928
  writeOut: (text2) => stdout += text2,
@@ -4915,34 +4957,34 @@ import { basename } from "node:path";
4915
4957
  function camel(segment) {
4916
4958
  return segment.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
4917
4959
  }
4918
- function scalar(option, raw) {
4919
- const type = option.type.replace("[]", "");
4920
- if (option.enum && !option.enum.includes(raw)) {
4960
+ function scalar(option2, raw) {
4961
+ const type = option2.type.replace("[]", "");
4962
+ if (option2.enum && !option2.enum.includes(raw)) {
4921
4963
  throw new UsageError(
4922
- `--${option.flag} must be one of ${option.enum.join(", ")}`
4964
+ `--${option2.flag} must be one of ${option2.enum.join(", ")}`
4923
4965
  );
4924
4966
  }
4925
4967
  if (type === "integer" || type === "number") {
4926
4968
  const value2 = Number(raw);
4927
4969
  if (raw.trim() === "" || Number.isNaN(value2) || type === "integer" && !Number.isInteger(value2)) {
4928
4970
  throw new UsageError(
4929
- `--${option.flag} must be ${type === "integer" ? "an integer" : "a number"}`
4971
+ `--${option2.flag} must be ${type === "integer" ? "an integer" : "a number"}`
4930
4972
  );
4931
4973
  }
4932
4974
  return value2;
4933
4975
  }
4934
4976
  if (type === "boolean") {
4935
4977
  if (raw !== "true" && raw !== "false") {
4936
- throw new UsageError(`--${option.flag} must be true or false`);
4978
+ throw new UsageError(`--${option2.flag} must be true or false`);
4937
4979
  }
4938
4980
  return raw === "true";
4939
4981
  }
4940
- if (type === "json") return parseJson(raw, `--${option.flag}`);
4982
+ if (type === "json") return parseJson(raw, `--${option2.flag}`);
4941
4983
  return raw;
4942
4984
  }
4943
- function value(option, raws) {
4944
- if (option.type.endsWith("[]")) return raws.map((raw) => scalar(option, raw));
4945
- return scalar(option, raws.at(-1));
4985
+ function value(option2, raws) {
4986
+ if (option2.type.endsWith("[]")) return raws.map((raw) => scalar(option2, raw));
4987
+ return scalar(option2, raws.at(-1));
4946
4988
  }
4947
4989
  var nodeReader = {
4948
4990
  text: async (path) => readFileSync(path, "utf8"),
@@ -4954,12 +4996,12 @@ async function readInput(raw, io) {
4954
4996
  return raw;
4955
4997
  }
4956
4998
  async function resolveInputs(command, parsed, io) {
4957
- const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((option) => option.type === "json").map((option) => option.flag);
4999
+ const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((option2) => option2.type === "json").map((option2) => option2.flag);
4958
5000
  const flags = new Map(parsed.flags);
4959
- for (const flag of jsonFlags) {
4960
- const raws = flags.get(flag);
5001
+ for (const flag2 of jsonFlags) {
5002
+ const raws = flags.get(flag2);
4961
5003
  if (raws)
4962
- flags.set(flag, await Promise.all(raws.map((raw) => readInput(raw, io))));
5004
+ flags.set(flag2, await Promise.all(raws.map((raw) => readInput(raw, io))));
4963
5005
  }
4964
5006
  const body = parsed.body === void 0 ? void 0 : await readInput(parsed.body, io);
4965
5007
  return { ...parsed, flags, body };
@@ -5009,18 +5051,18 @@ async function buildRequest(command, input, context = {}) {
5009
5051
  });
5010
5052
  const flags = new Map(parsed.flags);
5011
5053
  const query = new URLSearchParams();
5012
- for (const option of command.query) {
5013
- const raws = flags.get(option.flag);
5014
- flags.delete(option.flag);
5054
+ for (const option2 of command.query) {
5055
+ const raws = flags.get(option2.flag);
5056
+ flags.delete(option2.flag);
5015
5057
  if (!raws) {
5016
- if (option.required) throw new UsageError(`--${option.flag} is required`);
5058
+ if (option2.required) throw new UsageError(`--${option2.flag} is required`);
5017
5059
  continue;
5018
5060
  }
5019
- const parsedValue = value(option, raws);
5061
+ const parsedValue = value(option2, raws);
5020
5062
  const values = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
5021
- if (option.explode === false)
5022
- query.append(option.name, values.map(String).join(","));
5023
- else for (const v of values) query.append(option.name, String(v));
5063
+ if (option2.explode === false)
5064
+ query.append(option2.name, values.map(String).join(","));
5065
+ else for (const v of values) query.append(option2.name, String(v));
5024
5066
  }
5025
5067
  const built = { method: command.method, path, query };
5026
5068
  const body = command.body;
@@ -5070,11 +5112,11 @@ async function buildRequest(command, input, context = {}) {
5070
5112
  flags.delete(field.flag);
5071
5113
  json[field.name] = value(field, raws);
5072
5114
  }
5073
- for (const [flag, raws] of [...flags]) {
5074
- const [head, ...rest] = flag.split(".");
5115
+ for (const [flag2, raws] of [...flags]) {
5116
+ const [head, ...rest] = flag2.split(".");
5075
5117
  const field = body.fields.find((f) => f.flag === head);
5076
5118
  if (!field || rest.length === 0) continue;
5077
- flags.delete(flag);
5119
+ flags.delete(flag2);
5078
5120
  const current = json[field.name];
5079
5121
  json[field.name] = typeof current === "object" && current !== null ? current : {};
5080
5122
  setPath(json, [field.name, ...rest.map(camel)], loose(raws.at(-1)));
@@ -5105,12 +5147,784 @@ function rejectUnknown(flags) {
5105
5147
  );
5106
5148
  }
5107
5149
  }
5150
+
5151
+ // src/sync/commands.ts
5152
+ function option(name, type, description, extra = {}) {
5153
+ return { name, flag: name, type, required: false, description, ...extra };
5154
+ }
5155
+ var local = {
5156
+ method: "GET",
5157
+ path: "",
5158
+ binary: false,
5159
+ destructive: false,
5160
+ local: true
5161
+ };
5162
+ var SYNC_COMMANDS = [
5163
+ {
5164
+ ...local,
5165
+ command: "execution pull",
5166
+ tool: "execution_pull",
5167
+ title: "Pull an execution to disk",
5168
+ description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
5169
+ readOnly: true,
5170
+ args: [
5171
+ {
5172
+ name: "executionId",
5173
+ flag: "execution-id",
5174
+ type: "string",
5175
+ required: true
5176
+ }
5177
+ ],
5178
+ query: [
5179
+ option(
5180
+ "files",
5181
+ "string",
5182
+ "Only download files whose path matches this glob. --no-files skips downloads.",
5183
+ {
5184
+ negatable: true
5185
+ }
5186
+ ),
5187
+ option(
5188
+ "summary-only",
5189
+ "boolean",
5190
+ "Write summary.json only: no activities, no files."
5191
+ ),
5192
+ option(
5193
+ "max-file-size",
5194
+ "integer",
5195
+ "Skip files larger than this many bytes."
5196
+ ),
5197
+ option(
5198
+ "dir",
5199
+ "string",
5200
+ "Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
5201
+ )
5202
+ ]
5203
+ }
5204
+ ];
5205
+
5206
+ // src/commands.ts
5207
+ function withSyncCommands(commands) {
5208
+ return [...commands, ...SYNC_COMMANDS].sort(
5209
+ (a, b) => a.command.localeCompare(b.command)
5210
+ );
5211
+ }
5212
+ var ALL_COMMANDS = withSyncCommands(COMMANDS);
5213
+
5214
+ // src/sync/activities.ts
5215
+ function indentJson(data) {
5216
+ return JSON.stringify(data, null, 2).split("\n").map((line2) => ` ${line2}`).join("\n");
5217
+ }
5218
+ function action(label, payload) {
5219
+ const info = payload.info;
5220
+ const detail = info && Object.keys(info).length > 0 ? `
5221
+ info:
5222
+ ${indentJson(info)}` : "";
5223
+ return `[${label}: ${payload.actionName}]:${detail}
5224
+ `;
5225
+ }
5226
+ function line(payload) {
5227
+ switch (payload.activityType) {
5228
+ case "reasoning":
5229
+ return `[reasoning]:
5230
+ ${payload.reasoning}
5231
+ `;
5232
+ case "generic":
5233
+ return `[message]:
5234
+ ${payload.message}
5235
+ `;
5236
+ case "user_message_received":
5237
+ return `[user_message]:
5238
+ ${payload.message}
5239
+ `;
5240
+ case "action_started":
5241
+ return action("action_started", payload);
5242
+ case "action_completed":
5243
+ return action("action_completed", payload);
5244
+ case "action_failed":
5245
+ return `[action_failed: ${payload.actionName}]:
5246
+ failure: ${payload.message}
5247
+ `;
5248
+ case "compaction_performed":
5249
+ return `[compaction]: summary_tokens: ${payload.summaryTokens}, duration_ms: ${payload.durationMs}
5250
+ `;
5251
+ case "compaction_started":
5252
+ return "[compaction_started]\n";
5253
+ default: {
5254
+ const summary2 = payload.display?.summaryText;
5255
+ return summary2 ? `[${payload.activityType}]: ${summary2}
5256
+ ` : `[${payload.activityType}]
5257
+ `;
5258
+ }
5259
+ }
5260
+ }
5261
+ function formatActivitiesMarkdown(activities, status, createdAt) {
5262
+ const lines = [
5263
+ "# Execution Activities\n",
5264
+ `Status: ${status}`,
5265
+ `Started: ${createdAt}`
5266
+ ];
5267
+ let currentTurn = "";
5268
+ for (const { payload } of activities) {
5269
+ const turnId = payload.display?.turnId ?? "";
5270
+ if (turnId && turnId !== currentTurn) {
5271
+ currentTurn = turnId;
5272
+ lines.push(`
5273
+ ---
5274
+
5275
+ ## ${turnId}
5276
+ `);
5277
+ } else if (!currentTurn && !turnId) {
5278
+ currentTurn = "_started";
5279
+ lines.push("\n---\n");
5280
+ }
5281
+ lines.push(line(payload));
5282
+ }
5283
+ return lines.join("\n");
5284
+ }
5285
+
5286
+ // ../workflow-files/src/representation/name-charset.ts
5287
+ var NAME_CHARSET_RE = /^[A-Za-z0-9 !#$%&'()+,.;=@^`{}[\]~-]+$/;
5288
+ var WINDOWS_RESERVED = /* @__PURE__ */ new Set([
5289
+ "con",
5290
+ "prn",
5291
+ "aux",
5292
+ "nul",
5293
+ ...Array.from({ length: 9 }, (_, i) => `com${i + 1}`),
5294
+ ...Array.from({ length: 9 }, (_, i) => `lpt${i + 1}`)
5295
+ ]);
5296
+ function isNameCharsetConforming(name) {
5297
+ return NAME_CHARSET_RE.test(name);
5298
+ }
5299
+
5300
+ // ../workflow-files/src/representation/types.ts
5301
+ function normalizeNodeName(name) {
5302
+ let result = name.toLowerCase();
5303
+ result = result.replace(/[^a-z0-9]+/g, "_");
5304
+ result = result.replace(/^_+|_+$/g, "");
5305
+ if (result.length > 80) {
5306
+ result = result.slice(0, 80).replace(/_+$/, "");
5307
+ }
5308
+ if (result === "") {
5309
+ throw new Error(
5310
+ `representation: node name "${name}" produces an empty directory name after normalization`
5311
+ );
5312
+ }
5313
+ return result;
5314
+ }
5315
+ function nodeDirName(name) {
5316
+ if (isNameCharsetConforming(name)) {
5317
+ return name.replaceAll(" ", "_");
5318
+ }
5319
+ return normalizeNodeName(name);
5320
+ }
5321
+ function agentDirName(name) {
5322
+ try {
5323
+ return nodeDirName(name);
5324
+ } catch {
5325
+ return "agent";
5326
+ }
5327
+ }
5328
+ function executionDirName(executionId) {
5329
+ return executionId.slice(0, 8);
5330
+ }
5331
+ function workflowDirName(workflowId, version, parentId, parentVersion) {
5332
+ const short = workflowId.slice(0, 8);
5333
+ if (version !== void 0 && version !== null) {
5334
+ return `v${version}_${short}`;
5335
+ }
5336
+ if (parentId) {
5337
+ if (parentVersion !== void 0 && parentVersion !== null) {
5338
+ return `draft_${short}_from_v${parentVersion}`;
5339
+ }
5340
+ return `draft_${short}_from_draft_${parentId.slice(0, 8)}`;
5341
+ }
5342
+ return `draft_${short}`;
5343
+ }
5344
+
5345
+ // src/sync/paths.ts
5346
+ import { createHash } from "node:crypto";
5347
+ function join(...parts) {
5348
+ return parts.filter((part) => part !== "").join("/").replace(/\/{2,}/g, "/");
5349
+ }
5350
+ function workflowRoot(root, workflowName) {
5351
+ return join(root, agentDirName(workflowName));
5352
+ }
5353
+ function isContained(path) {
5354
+ if (path === "" || path.startsWith("/") || path.includes("\\")) return false;
5355
+ return path.split("/").every((segment) => segment !== ".." && segment !== "");
5356
+ }
5357
+ function sha256(bytes) {
5358
+ return createHash("sha256").update(bytes).digest("hex");
5359
+ }
5360
+ function matchesGlob(path, pattern) {
5361
+ let source = "";
5362
+ for (let i = 0; i < pattern.length; i++) {
5363
+ const ch = pattern[i];
5364
+ if (ch === "*" && pattern[i + 1] === "*" && pattern[i + 2] === "/") {
5365
+ source += "(?:.*/)?";
5366
+ i += 2;
5367
+ } else if (ch === "*" && pattern[i + 1] === "*") {
5368
+ source += ".*";
5369
+ i += 1;
5370
+ } else if (ch === "*") source += "[^/]*";
5371
+ else if (ch === "?") source += "[^/]";
5372
+ else source += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
5373
+ }
5374
+ return new RegExp(`^${source}$`).test(path);
5375
+ }
5376
+
5377
+ // src/sync/types.ts
5378
+ var MANIFEST = ".asteroid.json";
5379
+ function get(path, query = {}) {
5380
+ return { method: "GET", path, query: new URLSearchParams(query) };
5381
+ }
5382
+ function statusOf(error) {
5383
+ const status = error?.status;
5384
+ return typeof status === "number" ? status : void 0;
5385
+ }
5386
+
5387
+ // src/sync/execution-pull.ts
5388
+ var DIRECTORIES = ["workspace", "downloads", "shared"];
5389
+ function relativeFilePath(file) {
5390
+ const at = file.filePath.indexOf(`/${file.directory}/`);
5391
+ if (at === -1)
5392
+ return `${file.directory}/${file.filePath.split("/").pop() ?? "unknown"}`;
5393
+ return file.filePath.slice(at + 1).replace(/^workspace\/\.execution-context\//, "");
5394
+ }
5395
+ function executionDir(options, execution) {
5396
+ const chosen = options.dir?.(execution);
5397
+ if (chosen) return chosen;
5398
+ const root = workflowRoot(
5399
+ options.root,
5400
+ execution.workflowName ?? execution.workflowId
5401
+ );
5402
+ return join(root, "executions", executionDirName(execution.id));
5403
+ }
5404
+ async function writeText(sync, path, text2) {
5405
+ await sync.files.write(path, Buffer.from(text2, "utf8"));
5406
+ }
5407
+ async function writeActivities(sync, dir, execution) {
5408
+ try {
5409
+ const activities = await sync.remote.json(
5410
+ get(`/agents/v2/executions/${execution.id}/activities`, { order: "asc" })
5411
+ );
5412
+ if (activities.length === 0) return false;
5413
+ const markdown = formatActivitiesMarkdown(
5414
+ activities,
5415
+ execution.status,
5416
+ execution.createdAt
5417
+ );
5418
+ await writeText(sync, join(dir, "activities.md"), markdown);
5419
+ return true;
5420
+ } catch {
5421
+ return false;
5422
+ }
5423
+ }
5424
+ async function listFiles(sync, executionId) {
5425
+ const listing = await sync.remote.json(
5426
+ get(`/agents/v2/executions/${executionId}/files`)
5427
+ );
5428
+ return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
5429
+ }
5430
+ async function download(sync, executionId, file) {
5431
+ const path = `/agents/v2/executions/${executionId}/files/${file.id}/signed-url`;
5432
+ const { url } = await sync.remote.json(get(path));
5433
+ return sync.remote.download(url);
5434
+ }
5435
+ function selectFiles(files, options, skipped) {
5436
+ return files.filter((file) => {
5437
+ const path = relativeFilePath(file);
5438
+ if (!isContained(path)) skipped.push(`${path} (unsafe path)`);
5439
+ else if (options.glob !== void 0 && !matchesGlob(path, options.glob))
5440
+ skipped.push(`${path} (excluded by --files)`);
5441
+ else if (options.maxFileBytes !== void 0 && file.fileSize > options.maxFileBytes)
5442
+ skipped.push(`${path} (${file.fileSize} bytes, over the size limit)`);
5443
+ else return true;
5444
+ return false;
5445
+ });
5446
+ }
5447
+ async function pullFiles(sync, dir, options) {
5448
+ const skipped = [];
5449
+ const failed = [];
5450
+ let files;
5451
+ try {
5452
+ files = await listFiles(sync, options.executionId);
5453
+ } catch (error) {
5454
+ const reason = error instanceof Error ? error.message : String(error);
5455
+ return {
5456
+ downloaded: 0,
5457
+ candidates: 0,
5458
+ skipped,
5459
+ failed: [`file listing (${reason})`]
5460
+ };
5461
+ }
5462
+ let downloaded = 0;
5463
+ await Promise.all(
5464
+ selectFiles(files, options, skipped).map(async (file) => {
5465
+ try {
5466
+ const bytes = await download(sync, options.executionId, file);
5467
+ await sync.files.write(join(dir, relativeFilePath(file)), bytes);
5468
+ downloaded++;
5469
+ } catch (error) {
5470
+ failed.push(
5471
+ `${file.fileName} (${error instanceof Error ? error.message : String(error)})`
5472
+ );
5473
+ }
5474
+ })
5475
+ );
5476
+ return { downloaded, candidates: files.length, skipped, failed };
5477
+ }
5478
+ async function pullExecution(sync, options) {
5479
+ const execution = await sync.remote.json(
5480
+ get(`/agents/v2/executions/${options.executionId}`)
5481
+ );
5482
+ const dir = executionDir(options, execution);
5483
+ await writeText(
5484
+ sync,
5485
+ join(dir, "summary.json"),
5486
+ JSON.stringify(execution, null, 2)
5487
+ );
5488
+ const result = {
5489
+ execution,
5490
+ dir,
5491
+ written: ["summary.json"],
5492
+ downloaded: 0,
5493
+ candidates: 0,
5494
+ skipped: [],
5495
+ failed: []
5496
+ };
5497
+ if (execution.purgedAt) return result;
5498
+ if (options.activities !== false && await writeActivities(sync, dir, execution))
5499
+ result.written.push("activities.md");
5500
+ if (options.files === false) {
5501
+ return {
5502
+ ...result,
5503
+ skippedBy: options.activities === false ? "--summary-only" : "--no-files"
5504
+ };
5505
+ }
5506
+ return { ...result, ...await pullFiles(sync, dir, options) };
5507
+ }
5508
+ function filesLine(result) {
5509
+ const parts = [...result.written];
5510
+ if (result.downloaded > 0) parts.push(`${result.downloaded} downloaded`);
5511
+ const counted = `Files: ${result.written.length + result.downloaded} written (${parts.join(", ")})`;
5512
+ if (result.skippedBy)
5513
+ return `${counted}; downloads skipped (${result.skippedBy})`;
5514
+ if (result.candidates === 0) return `${counted}; no execution files matched`;
5515
+ return counted;
5516
+ }
5517
+ function failures(execution) {
5518
+ const list = execution.scriptFailures ?? [];
5519
+ if (list.length === 0) return null;
5520
+ const lines = list.map(
5521
+ (f) => ` - ${f.nodeName ?? "(unknown node)"}: ${(f.error ?? "").split("\n")[0]}`
5522
+ );
5523
+ return `Script failures: ${list.length}
5524
+ ${lines.join("\n")}`;
5525
+ }
5526
+ function purged(result) {
5527
+ const { execution } = result;
5528
+ return [
5529
+ `Execution: ${execution.id}`,
5530
+ `Status: ${execution.status}`,
5531
+ `Purged: ${execution.purgedAt}`,
5532
+ "This run was retention-purged. Activities, files, inputs and result are gone. Do not diagnose it.",
5533
+ `Written to: ${result.dir}/`
5534
+ ].join("\n");
5535
+ }
5536
+ function formatExecutionPull(result) {
5537
+ const { execution } = result;
5538
+ if (execution.purgedAt) return purged(result);
5539
+ const outcome = execution.executionResult;
5540
+ return [
5541
+ `Execution: ${execution.id}`,
5542
+ `Status: ${execution.status}`,
5543
+ `Workflow: ${execution.workflowName ?? execution.workflowId}`,
5544
+ execution.workflowVersionId ? `Workflow version: ${execution.workflowVersionId}` : null,
5545
+ execution.inputs ? `Inputs: ${JSON.stringify(execution.inputs)}` : null,
5546
+ outcome ? `Result: ${outcome.outcome}: ${outcome.reasoning}` : null,
5547
+ `Created: ${execution.createdAt}`,
5548
+ execution.terminalAt ? `Completed: ${execution.terminalAt}` : null,
5549
+ execution.duration != null ? `Duration: ${execution.duration.toFixed(1)}s` : null,
5550
+ failures(execution),
5551
+ filesLine(result),
5552
+ result.skipped.length > 0 ? `Skipped:
5553
+ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
5554
+ result.failed.length > 0 ? `Failed: ${result.failed.join(", ")}` : null,
5555
+ `Written to: ${result.dir}/`
5556
+ ].filter(Boolean).join("\n");
5557
+ }
5558
+
5559
+ // src/sync/node-files.ts
5560
+ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
5561
+ import { dirname, relative, resolve, sep } from "node:path";
5562
+ function missing(error) {
5563
+ return error.code === "ENOENT";
5564
+ }
5565
+ async function walk(root, dir, out) {
5566
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
5567
+ const path = resolve(dir, entry.name);
5568
+ if (entry.isDirectory()) await walk(root, path, out);
5569
+ else if (entry.isFile())
5570
+ out.push(relative(root, path).split(sep).join("/"));
5571
+ }
5572
+ }
5573
+ function nodeFiles(cwd) {
5574
+ const at = (path) => resolve(cwd, path);
5575
+ return {
5576
+ async read(path) {
5577
+ try {
5578
+ return new Uint8Array(await readFile(at(path)));
5579
+ } catch (error) {
5580
+ if (missing(error)) return void 0;
5581
+ throw error;
5582
+ }
5583
+ },
5584
+ async write(path, bytes) {
5585
+ await mkdir(dirname(at(path)), { recursive: true });
5586
+ await writeFile(at(path), bytes);
5587
+ },
5588
+ async remove(path) {
5589
+ await rm(at(path), { force: true });
5590
+ },
5591
+ async list(dir) {
5592
+ const out = [];
5593
+ try {
5594
+ await walk(at(dir), at(dir), out);
5595
+ } catch (error) {
5596
+ if (!missing(error)) throw error;
5597
+ }
5598
+ return out.sort();
5599
+ }
5600
+ };
5601
+ }
5602
+
5603
+ // src/sync/workflow-pull.ts
5604
+ var CONCURRENCY = 16;
5605
+ function pullDir(root, tree, head) {
5606
+ const base = workflowRoot(root, tree.workflowName);
5607
+ if (head) return join(base, "head");
5608
+ return join(base, ".versions", workflowDirName(tree.versionId, tree.version));
5609
+ }
5610
+ async function agentFileBytes(sync, workflowId, tree, entry) {
5611
+ const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
5612
+ const { url } = await sync.remote.json(get(path));
5613
+ return sync.remote.download(url);
5614
+ }
5615
+ async function entryBytes(sync, workflowId, tree, entry) {
5616
+ if (entry.contentBase64 !== void 0)
5617
+ return Buffer.from(entry.contentBase64, "base64");
5618
+ if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
5619
+ if (!entry.fileId) return void 0;
5620
+ return agentFileBytes(sync, workflowId, tree, entry);
5621
+ }
5622
+ async function writeEntries(sync, options, tree, dir) {
5623
+ const hashes = {};
5624
+ const skipped = [];
5625
+ let shared = 0;
5626
+ const wanted = tree.files.filter((entry) => {
5627
+ if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
5628
+ else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
5629
+ skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
5630
+ else return true;
5631
+ return false;
5632
+ });
5633
+ for (let i = 0; i < wanted.length; i += CONCURRENCY) {
5634
+ await Promise.all(
5635
+ wanted.slice(i, i + CONCURRENCY).map(async (entry) => {
5636
+ const bytes = await entryBytes(sync, options.workflowId, tree, entry);
5637
+ if (!bytes) return void skipped.push(`${entry.path} (no content)`);
5638
+ await sync.files.write(join(dir, entry.path), bytes);
5639
+ hashes[entry.path] = sha256(bytes);
5640
+ if (entry.kind === "agent_file") shared++;
5641
+ })
5642
+ );
5643
+ }
5644
+ return { hashes, skipped, shared };
5645
+ }
5646
+ async function removeStale(sync, dir, keep) {
5647
+ for (const path of await sync.files.list(dir)) {
5648
+ if (!keep.has(path) && path !== MANIFEST)
5649
+ await sync.files.remove(join(dir, path));
5650
+ }
5651
+ }
5652
+ async function writeManifest(sync, dir, manifest) {
5653
+ const text2 = `${JSON.stringify(manifest, null, 2)}
5654
+ `;
5655
+ await sync.files.write(join(dir, MANIFEST), Buffer.from(text2, "utf8"));
5656
+ }
5657
+ async function pullWorkflow(sync, options) {
5658
+ const request = {
5659
+ ...options.request,
5660
+ query: new URLSearchParams(options.request.query)
5661
+ };
5662
+ request.query.set("contents", "all");
5663
+ const tree = await sync.remote.json(request);
5664
+ const head = request.path.endsWith("/head/files");
5665
+ const dir = options.dir ?? pullDir(options.root ?? "", tree, head);
5666
+ const { hashes, skipped, shared } = await writeEntries(
5667
+ sync,
5668
+ options,
5669
+ tree,
5670
+ dir
5671
+ );
5672
+ await removeStale(sync, dir, new Set(Object.keys(hashes)));
5673
+ if (options.manifest !== false) {
5674
+ await writeManifest(sync, dir, {
5675
+ workflowId: options.workflowId,
5676
+ versionId: tree.versionId,
5677
+ head,
5678
+ rev: tree.rev,
5679
+ files: hashes
5680
+ });
5681
+ }
5682
+ return {
5683
+ workflowId: options.workflowId,
5684
+ workflowName: tree.workflowName,
5685
+ versionId: tree.versionId,
5686
+ version: tree.version,
5687
+ rev: tree.rev,
5688
+ dir,
5689
+ head,
5690
+ written: Object.keys(hashes).length,
5691
+ shared,
5692
+ skipped
5693
+ };
5694
+ }
5695
+ function formatWorkflowPull(result) {
5696
+ const definition = result.written - result.shared;
5697
+ const files = result.written === 0 ? "Files: none written" : result.shared === 0 ? `Files: ${result.written} written` : `Files: ${result.written} written (${definition} workflow definition, ${result.shared} shared)`;
5698
+ return [
5699
+ `Workflow: ${result.workflowName} (${result.workflowId})`,
5700
+ `Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
5701
+ `Written to: ${result.dir}/`,
5702
+ files,
5703
+ result.skipped.length > 0 ? `Skipped:
5704
+ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
5705
+ result.head ? `Edit the files, then run \`asteroid workflow push\` from ${result.dir}/.` : "This is a pinned version. It is read-only."
5706
+ ].filter(Boolean).join("\n");
5707
+ }
5708
+
5709
+ // src/sync/workflow-push.ts
5710
+ async function readManifest(sync, dir) {
5711
+ const bytes = await sync.files.read(join(dir, MANIFEST));
5712
+ if (!bytes) {
5713
+ throw new UsageError(
5714
+ `no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
5715
+ );
5716
+ }
5717
+ const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
5718
+ if (!manifest.head) {
5719
+ throw new UsageError(
5720
+ `${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
5721
+ );
5722
+ }
5723
+ return manifest;
5724
+ }
5725
+ async function localChanges(sync, dir, manifest) {
5726
+ const local2 = (await sync.files.list(dir)).filter(
5727
+ (path) => path !== MANIFEST
5728
+ );
5729
+ const writes = [];
5730
+ for (const path of local2) {
5731
+ const bytes = await sync.files.read(join(dir, path));
5732
+ if (!bytes) continue;
5733
+ const hash = sha256(bytes);
5734
+ if (manifest.files[path] === hash) continue;
5735
+ writes.push({
5736
+ path,
5737
+ contentBase64: Buffer.from(bytes).toString("base64"),
5738
+ hash
5739
+ });
5740
+ }
5741
+ const present = new Set(local2);
5742
+ const deletes = Object.keys(manifest.files).filter(
5743
+ (path) => !present.has(path)
5744
+ );
5745
+ return { writes, deletes };
5746
+ }
5747
+ async function patch(sync, manifest, writes, deletes) {
5748
+ try {
5749
+ await sync.remote.json({
5750
+ method: "PATCH",
5751
+ path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
5752
+ query: new URLSearchParams(),
5753
+ json: { baseRev: manifest.rev, writes, deletes }
5754
+ });
5755
+ } catch (error) {
5756
+ if (statusOf(error) !== 409) throw error;
5757
+ throw new UsageError(
5758
+ `the workflow head changed since your pull (rev ${manifest.rev}). Copy your edits aside, run \`asteroid workflow pull ${manifest.workflowId}\`, reapply them, then push again.`
5759
+ );
5760
+ }
5761
+ }
5762
+ async function refresh(sync, dir, manifest, pushed) {
5763
+ const tree = await sync.remote.json(
5764
+ get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
5765
+ );
5766
+ const files = {};
5767
+ for (const entry of tree.files) {
5768
+ if (entry.content !== void 0) {
5769
+ const bytes = Buffer.from(entry.content, "utf8");
5770
+ files[entry.path] = sha256(bytes);
5771
+ if (pushed.get(entry.path) !== files[entry.path])
5772
+ await sync.files.write(join(dir, entry.path), bytes);
5773
+ } else {
5774
+ const local2 = await sync.files.read(join(dir, entry.path));
5775
+ const hash = local2 ? sha256(local2) : pushed.get(entry.path) ?? entry.checksum;
5776
+ if (hash) files[entry.path] = hash;
5777
+ }
5778
+ }
5779
+ for (const path of await sync.files.list(dir)) {
5780
+ if (path !== MANIFEST && !(path in files))
5781
+ await sync.files.remove(join(dir, path));
5782
+ }
5783
+ const next = { ...manifest, versionId: tree.versionId, rev: tree.rev, files };
5784
+ await writeManifest(sync, dir, next);
5785
+ return next;
5786
+ }
5787
+ async function pushWorkflow(sync, dir, workflowId) {
5788
+ const manifest = await readManifest(sync, dir);
5789
+ if (workflowId !== void 0 && manifest.workflowId !== workflowId) {
5790
+ throw new UsageError(
5791
+ `${dir} holds workflow ${manifest.workflowId}, not ${workflowId}.`
5792
+ );
5793
+ }
5794
+ const { writes, deletes } = await localChanges(sync, dir, manifest);
5795
+ const result = {
5796
+ workflowId: manifest.workflowId,
5797
+ dir,
5798
+ written: writes.map((w) => w.path),
5799
+ deleted: deletes
5800
+ };
5801
+ if (writes.length === 0 && deletes.length === 0) {
5802
+ return { ...result, rev: manifest.rev, versionId: manifest.versionId };
5803
+ }
5804
+ await patch(
5805
+ sync,
5806
+ manifest,
5807
+ writes.map(({ path, contentBase64 }) => ({ path, contentBase64 })),
5808
+ deletes
5809
+ );
5810
+ const next = await refresh(
5811
+ sync,
5812
+ dir,
5813
+ manifest,
5814
+ new Map(writes.map((w) => [w.path, w.hash]))
5815
+ );
5816
+ return { ...result, rev: next.rev, versionId: next.versionId };
5817
+ }
5818
+ function formatWorkflowPush(result) {
5819
+ if (result.written.length === 0 && result.deleted.length === 0) {
5820
+ return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).`;
5821
+ }
5822
+ return [
5823
+ `Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.`,
5824
+ ...result.written.map((path) => ` + ${path}`),
5825
+ ...result.deleted.map((path) => ` - ${path}`),
5826
+ `Head is now rev ${result.rev} (version ${result.versionId}).`
5827
+ ].join("\n");
5828
+ }
5829
+
5830
+ // src/sync/run.ts
5831
+ function flag(parsed, name) {
5832
+ return parsed.flags.get(name)?.at(-1);
5833
+ }
5834
+ function integer(parsed, name) {
5835
+ const raw = flag(parsed, name);
5836
+ if (raw === void 0) return void 0;
5837
+ if (!/^\d+$/.test(raw))
5838
+ throw new UsageError(`--${name} must be a whole number`);
5839
+ return Number(raw);
5840
+ }
5841
+ function executionOptions(parsed) {
5842
+ const summaryOnly = flag(parsed, "summary-only") === "true";
5843
+ const glob = flag(parsed, "files");
5844
+ const dir = flag(parsed, "dir");
5845
+ return {
5846
+ executionId: parsed.positionals[0],
5847
+ activities: !summaryOnly,
5848
+ files: !summaryOnly && glob !== "false",
5849
+ glob: glob === "false" ? void 0 : glob,
5850
+ maxFileBytes: integer(parsed, "max-file-size"),
5851
+ dir: dir ? () => dir : void 0
5852
+ };
5853
+ }
5854
+ function withoutFlag(parsed, name) {
5855
+ const flags = new Map(parsed.flags);
5856
+ flags.delete(name);
5857
+ return { ...parsed, flags };
5858
+ }
5859
+ var PARTIAL = ["paths", "variants"];
5860
+ function rejectPartial(parsed) {
5861
+ const used = PARTIAL.filter((name) => parsed.flags.has(name));
5862
+ if (used.length === 0) return;
5863
+ throw new UsageError(
5864
+ `pull writes the whole tree, so it cannot take ${used.map((name) => `--${name}`).join(" or ")}. Add --json to list those files instead.`
5865
+ );
5866
+ }
5867
+ async function pull(command, parsed, sync, deps) {
5868
+ rejectPartial(parsed);
5869
+ const result = await pullWorkflow(sync, {
5870
+ workflowId: parsed.positionals[0],
5871
+ request: await buildRequest(command, withoutFlag(parsed, "out"), {
5872
+ io: deps.io
5873
+ }),
5874
+ dir: flag(parsed, "out"),
5875
+ root: deps.root ?? "",
5876
+ ...deps.workflowPull
5877
+ });
5878
+ return { result, text: formatWorkflowPull(result) };
5879
+ }
5880
+ async function execute(command, parsed, sync, deps) {
5881
+ if (command.files === "pull") return pull(command, parsed, sync, deps);
5882
+ if (command.files === "push") {
5883
+ const result2 = await pushWorkflow(
5884
+ sync,
5885
+ flag(parsed, "from") ?? ".",
5886
+ parsed.positionals[0] || void 0
5887
+ );
5888
+ return { result: result2, text: formatWorkflowPush(result2) };
5889
+ }
5890
+ if (command.command !== "execution pull")
5891
+ throw new UsageError(`unknown command ${command.command}`);
5892
+ const result = await pullExecution(sync, {
5893
+ ...executionOptions(parsed),
5894
+ root: deps.root ?? "",
5895
+ ...deps.executionPull
5896
+ });
5897
+ return { result, text: formatExecutionPull(result) };
5898
+ }
5899
+ async function runSync(command, parsed, deps) {
5900
+ const sync = {
5901
+ remote: deps.remote,
5902
+ files: deps.files ?? nodeFiles(deps.cwd)
5903
+ };
5904
+ const { result, text: text2 } = await execute(command, parsed, sync, deps);
5905
+ deps.stdout(`${parsed.json ? JSON.stringify(result, null, 2) : text2}
5906
+ `);
5907
+ return 0;
5908
+ }
5108
5909
  export {
5910
+ ALL_COMMANDS,
5109
5911
  COMMANDS,
5912
+ MANIFEST,
5913
+ SYNC_COMMANDS,
5110
5914
  UsageError,
5111
5915
  buildRequest,
5916
+ formatActivitiesMarkdown,
5917
+ formatExecutionPull,
5918
+ formatWorkflowPull,
5919
+ formatWorkflowPush,
5112
5920
  leadingWords,
5113
5921
  matchCommand,
5922
+ matchesGlob,
5114
5923
  parseInvocation,
5115
- render
5924
+ pullExecution,
5925
+ pullWorkflow,
5926
+ pushWorkflow,
5927
+ render,
5928
+ runSync,
5929
+ withSyncCommands
5116
5930
  };