@everfir/az8-cli 0.2.0-preview.4 → 0.2.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/CHANGELOG.md CHANGED
@@ -3,6 +3,39 @@
3
3
  All notable AZ8 CLI changes are recorded here. The CLI package has its own linear version sequence;
4
4
  it does not inherit the Web application or monorepo root version.
5
5
 
6
+ ## 0.2.0 - 2026-07-22
7
+
8
+ First stable semantic Project Canvas client for agents.
9
+
10
+ ### Added
11
+
12
+ - Project management, persistent NDJSON Canvas collaboration, one-shot compatibility wrappers,
13
+ semantic note and hierarchy authoring, Core Node generation, Workflow observation, and safe local
14
+ media upload/download.
15
+ - A runtime-discoverable built-in agent Skill covering platform concepts, capability discovery,
16
+ persistent request/process lifecycle, generation sequencing, recovery, and prohibited bypasses.
17
+
18
+ ### Stability contract
19
+
20
+ - Public Canvas writes remain available only through semantic Operations and internal Commands;
21
+ callers cannot write raw backend, Yjs, Core Node, or Project Content data.
22
+ - Protocol `1.0`, ordered request/Receipt behavior, authenticated presence, no automatic write retry,
23
+ and explicit deferred scopes form the initial stable compatibility boundary.
24
+ - 3D World, Asset aggregate management, automatic layout, media replacement, and batch upload remain
25
+ intentionally deferred rather than partially exposed.
26
+
27
+ ## 0.2.0-preview.5 - 2026-07-22
28
+
29
+ ### Changed
30
+
31
+ - Canvas awareness names now preserve the authenticated user's Web-equivalent display name first
32
+ and append a CLI-owned parenthesized client suffix, defaulting to `(cli)`.
33
+ - `--client-name <suffix>` may override the client suffix for persistent and one-shot lifecycles,
34
+ but caller-provided parentheses are rejected so presence cannot obscure the name boundary.
35
+ - CLI attention awareness now publishes only the semantic cursor and omits selection bounds.
36
+ - `startGeneration` now carries valid Workflow execution-time estimates into the View Node's
37
+ generation history as Web-compatible median and maximum completion timestamps.
38
+
6
39
  ## 0.2.0-preview.4 - 2026-07-22
7
40
 
8
41
  ### Changed
package/README.md CHANGED
@@ -3,12 +3,12 @@
3
3
  AZ8 CLI is a semantic Project Canvas client for agents. It does not import Web implementation code
4
4
  and does not expose raw backend, Yjs, or document mutation commands.
5
5
 
6
- ## Install the internal preview
6
+ ## Install
7
7
 
8
- AZ8 CLI requires Node.js 20 or newer. Install the preview channel from the configured npm registry:
8
+ AZ8 CLI requires Node.js 20 or newer. Install the stable channel from the configured npm registry:
9
9
 
10
10
  ```bash
11
- npm install --global @everfir/az8-cli@preview
11
+ npm install --global @everfir/az8-cli
12
12
  az8 --version
13
13
  az8 help
14
14
  az8 guide
@@ -17,12 +17,12 @@ az8 guide
17
17
  For a supplied release artifact, install the exact immutable tarball instead:
18
18
 
19
19
  ```bash
20
- npm install --global ./everfir-az8-cli-0.2.0-preview.4.tgz
20
+ npm install --global ./everfir-az8-cli-0.2.0.tgz
21
21
  az8 --version
22
22
  ```
23
23
 
24
- `az8 --version` must report the version selected for the agent environment. Preview releases do not
25
- replace npm's `latest` channel. Pin an exact version or tarball for reproducible agent runs.
24
+ `az8 --version` must report the version selected for the agent environment. Pin an exact version or
25
+ tarball for reproducible agent runs.
26
26
 
27
27
  ## Built-in agent Skill
28
28
 
@@ -124,7 +124,10 @@ az8 canvas operation <project-id> startGeneration \
124
124
  Operation input is one JSON object supplied by exactly one of `--input-stdin`, `--input-file`, or
125
125
  `--input-json`; omitted input defaults to `{}`. Input is limited to one MiB. `--timeout-ms` controls
126
126
  the same Operation acknowledgement or domain observation window as persistent stdio, and
127
- `--client-name` changes only the ephemeral awareness display name.
127
+ `--client-name` changes only the parenthesized client suffix of the ephemeral awareness name. The
128
+ authenticated display name always remains first: the default is `<current user> (cli)`, while
129
+ `--client-name codex` produces `<current user> (codex)`. Parentheses are owned by the CLI and are
130
+ rejected in caller-provided suffixes.
128
131
 
129
132
  Success writes one `az8.canvas.one-shot.v1` JSON document to stdout and exits `0`. A definitive
130
133
  input, validation, permission, or business failure writes structured JSON to stderr and exits `1`.
@@ -216,7 +219,7 @@ Canvas query, keep capability discovery and all ordered Operations in the same l
216
219
  it down only after the authoring task is complete.
217
220
 
218
221
  ```bash
219
- az8 canvas open <project-id> --write --format ndjson
222
+ az8 canvas open <project-id> --write --format ndjson [--client-name codex]
220
223
  ```
221
224
 
222
225
  The process writes one `hello` frame, then waits for an `initialize` request on stdin:
@@ -226,9 +229,12 @@ The process writes one `hello` frame, then waits for an `initialize` request on
226
229
  ```
227
230
 
228
231
  Before readiness the client hydrates the current authenticated account profile. Canvas Operations use
229
- that immutable account id and display name for persisted creator data. The `client.name` supplied by
230
- `initialize` is awareness-only and cannot impersonate or rename the persisted author. Project owner
231
- metadata is likewise never treated as the current actor on shared Projects.
232
+ that immutable account id and display name for persisted creator data. Awareness always displays the
233
+ authenticated name first and a CLI-owned parenthesized client suffix second. The default suffix is
234
+ `cli`; startup `--client-name` overrides it, otherwise `initialize.client.name` may provide it.
235
+ Caller suffixes cannot contain parentheses. This name is awareness-only and cannot impersonate or
236
+ rename the persisted author. The CLI publishes its Attention Cursor but deliberately omits selection
237
+ bounds. Project owner metadata is likewise never treated as the current actor on shared Projects.
232
238
 
233
239
  After `ready`, requests use `query`, `configureObservations`, `operation`, `reconnect`, or `shutdown`.
234
240
  The public authoring surface includes the note lifecycle, non-World View Node hierarchy operations,
@@ -311,6 +317,9 @@ explicitly submitting the same input. An uncertain backend or collaboration outc
311
317
  are acknowledged. It first estimates the exact current Draft inputs, then materializes literal Core
312
318
  Nodes and starts with the estimate's cost and Definition version. This estimate is internal: there is
313
319
  no public estimate Operation, balance query, caller-supplied expected cost, or second confirmation.
320
+ When the estimate includes execution-time ranges, the generation history records Web-compatible
321
+ median and maximum completion timestamps from the pre-submit time so Web can render the same expected
322
+ generation duration for a CLI-started Run.
314
323
  Waiting for a terminal result is deliberately a separate operation. Generation is a non-undoable
315
324
  Canvas History barrier. The CLI does not expose Assets, arbitrary Core Node creation, or 3D World
316
325
  operations.
@@ -340,9 +349,9 @@ Every output frame has `protocolVersion`, monotonic `streamSeq`, `type`, `payloa
340
349
  after collaboration-server acknowledgement. A timeout or lost acknowledgement is indeterminate,
341
350
  moves the Canvas context offline, and requires explicit reconnect and inspection before any retry.
342
351
 
343
- ## Preview acceptance checklist
352
+ ## Release acceptance checklist
344
353
 
345
- Before distributing a preview tarball, verify all of the following from its clean installation, not
354
+ Before distributing a release tarball, verify all of the following from its clean installation, not
346
355
  from TypeScript source:
347
356
 
348
357
  1. `az8 --version` matches the immutable package version; `az8 help` lists the built-in guide,
package/RELEASE.md CHANGED
@@ -1,7 +1,7 @@
1
- # AZ8 CLI preview release
1
+ # AZ8 CLI release
2
2
 
3
- This package is an internal preview. `preview` is the release channel; it must not replace npm's
4
- `latest` tag until a separate stability decision is made.
3
+ `latest` is the stable release channel. Preview versions remain immutable historical artifacts and
4
+ must not be republished or renamed into a stable version.
5
5
 
6
6
  ## Release gate
7
7
 
@@ -12,9 +12,11 @@ pnpm release:az8-cli
12
12
  pnpm release:pack:az8-cli
13
13
  ```
14
14
 
15
- The first command runs CLI tests, type checking, builds a real npm tarball, installs that tarball in
16
- a clean temporary directory, and verifies the installed `az8` executable. The second preserves the
17
- verified tarball and clean installation under `tmp/az8-cli-release/` for live acceptance.
15
+ The first command rebuilds the CLI's classified workspace dependencies, runs CLI tests and type
16
+ checking, builds a real npm tarball, installs that tarball in a clean temporary directory, and
17
+ verifies the installed `az8` executable. The second repeats the dependency-aware prepack and
18
+ preserves the verified tarball and clean installation under `tmp/az8-cli-release/` for live
19
+ acceptance. Never bundle against pre-existing workspace `dist/` output.
18
20
 
19
21
  Run the live smoke only against the test environment. It creates one Project unless `--project-id`
20
22
  reuses an explicit test Project, starts one image generation, and uploads one tiny PNG:
@@ -28,28 +30,45 @@ node apps/az8-cli/scripts/smoke-test.js \
28
30
  The smoke report must show authenticated persistent stdio, ordered shutdown, a completed Workflow
29
31
  Run whose target resolves its output Core Node, upload replay without another transfer, and matching
30
32
  download SHA-256. `--skip-generation` is only for diagnosing unrelated media or transport checks;
31
- it cannot satisfy full release acceptance.
33
+ it cannot satisfy full release acceptance. When the release changes generation-time projection,
34
+ select a timing-enabled test Definition and require its persisted Web estimate:
35
+
36
+ ```bash
37
+ node apps/az8-cli/scripts/smoke-test.js \
38
+ --bin ./tmp/az8-cli-release/install/node_modules/.bin/az8 \
39
+ --env-file ./.env.local \
40
+ --generation-definition-id <timing-enabled-test-definition-id> \
41
+ --require-generation-estimate
42
+ ```
32
43
 
33
44
  Before publishing, also run the test-environment acceptance checklist in the packaged README and the
34
45
  repository-wide `pnpm verify`. Publishing is an external action and requires explicit operator
35
46
  authorization:
36
47
 
37
48
  ```bash
38
- npm publish ./tmp/az8-cli-release/everfir-az8-cli-0.2.0-preview.4.tgz --tag preview
49
+ npm publish ./tmp/az8-cli-release/everfir-az8-cli-0.2.0.tgz --tag latest
39
50
  npm view @everfir/az8-cli dist-tags versions --json
40
51
  ```
41
52
 
42
- Do not pass `--tag latest`. Do not publish from an unverified source directory.
53
+ Publish only the exact retained tarball whose SHA-512 appears in the stable release verification
54
+ record. Do not publish from an unverified source directory. If the obsolete misspelled `prewview`
55
+ tag still exists after stable publication, remove that tag without unpublishing its immutable
56
+ version:
57
+
58
+ ```bash
59
+ npm dist-tag rm @everfir/az8-cli prewview
60
+ npm view @everfir/az8-cli dist-tags --json
61
+ ```
43
62
 
44
63
  ## Rollback
45
64
 
46
- Published versions are immutable. Rollback moves the `preview` tag to a previously verified version
65
+ Published versions are immutable. Rollback moves `latest` to a previously verified stable version
47
66
  instead of unpublishing or overwriting a package:
48
67
 
49
68
  ```bash
50
- npm dist-tag add @everfir/az8-cli@<previous-version> preview
69
+ npm dist-tag add @everfir/az8-cli@<previous-stable-version> latest
51
70
  npm view @everfir/az8-cli dist-tags --json
52
- npm install --global @everfir/az8-cli@preview
71
+ npm install --global @everfir/az8-cli
53
72
  az8 --version
54
73
  ```
55
74
 
package/dist/main.js CHANGED
@@ -4527,6 +4527,27 @@ var require_src2 = __commonJS({
4527
4527
  }
4528
4528
  });
4529
4529
 
4530
+ // src/canvas-awareness.ts
4531
+ var DEFAULT_CANVAS_CLIENT_NAME = "cli";
4532
+ function normalizeCanvasClientName(value) {
4533
+ const name = (value ?? DEFAULT_CANVAS_CLIENT_NAME).trim();
4534
+ if (!name) {
4535
+ throw invalidClientName("Canvas client name must not be empty.");
4536
+ }
4537
+ if (/[()]/.test(name)) {
4538
+ throw invalidClientName(
4539
+ "Canvas client name must not contain parentheses; AZ8 adds the required (name) suffix."
4540
+ );
4541
+ }
4542
+ return name;
4543
+ }
4544
+ function formatCanvasAwarenessName(actorDisplayName, clientName) {
4545
+ return `${actorDisplayName} (${normalizeCanvasClientName(clientName)})`;
4546
+ }
4547
+ function invalidClientName(message) {
4548
+ return Object.assign(new Error(message), { code: "invalid-client-name" });
4549
+ }
4550
+
4530
4551
  // ../../node_modules/.pnpm/lib0@0.2.117/node_modules/lib0/math.js
4531
4552
  var floor = Math.floor;
4532
4553
  var abs = Math.abs;
@@ -13579,10 +13600,7 @@ var HocuspocusProjectCanvasDocumentImplementation = class {
13579
13600
  this.setAwarenessFields({
13580
13601
  cursor: input.cursor,
13581
13602
  updatedAt: Date.now(),
13582
- view: {
13583
- kind: "canvas",
13584
- ...input.selection ? { selection: input.selection } : {}
13585
- }
13603
+ view: { kind: "canvas" }
13586
13604
  });
13587
13605
  }
13588
13606
  getAwareness() {
@@ -15916,6 +15934,7 @@ var CoreNodeCreativeLoop = class {
15916
15934
  prepared,
15917
15935
  timeoutMs
15918
15936
  });
15937
+ const submittedAt = this.context.now();
15919
15938
  const run = await this.systems.workflowRuntime.startRun({
15920
15939
  definitionId: definition.id,
15921
15940
  expectedCost: estimate.estimatedCost,
@@ -15933,11 +15952,13 @@ var CoreNodeCreativeLoop = class {
15933
15952
  runtime: {
15934
15953
  expectedCost: estimate.estimatedCost,
15935
15954
  expectedDefinitionVersion: estimate.definitionVersion,
15955
+ ...createEstimatedCompletionTimes(estimate, submittedAt),
15936
15956
  instanceIds: [run.id],
15937
15957
  materializedInputs: materialized.materializedInputs,
15938
15958
  originClientId: this.context.clientInstanceId,
15939
- submittedAt: timestamp
15940
- }
15959
+ submittedAt
15960
+ },
15961
+ updatedAt: timestamp
15941
15962
  };
15942
15963
  const { creationIntent: _creationIntent, ...data } = asRecord3(target.data);
15943
15964
  const after = {
@@ -16224,6 +16245,28 @@ var CoreNodeCreativeLoop = class {
16224
16245
  };
16225
16246
  }
16226
16247
  };
16248
+ function createEstimatedCompletionTimes(estimate, submittedAt) {
16249
+ const ranges = estimate.executionTimeRanges;
16250
+ if (!ranges?.length)
16251
+ return {};
16252
+ let medianSeconds = 0;
16253
+ let maxSeconds = 0;
16254
+ let validRangeCount = 0;
16255
+ for (const range of ranges) {
16256
+ if (!Number.isFinite(range.medianSeconds) || range.medianSeconds < 0 || !Number.isFinite(range.maxSeconds) || range.maxSeconds < 0) {
16257
+ continue;
16258
+ }
16259
+ medianSeconds += range.medianSeconds;
16260
+ maxSeconds += range.maxSeconds;
16261
+ validRangeCount += 1;
16262
+ }
16263
+ if (validRangeCount === 0)
16264
+ return {};
16265
+ return {
16266
+ estimatedMaxCompletionAt: submittedAt + maxSeconds * 1e3,
16267
+ estimatedMedianCompletionAt: submittedAt + medianSeconds * 1e3
16268
+ };
16269
+ }
16227
16270
  function attention(viewNodeId, result3) {
16228
16271
  return { attention: { kind: "view-nodes", viewNodeIds: [viewNodeId] }, result: result3 };
16229
16272
  }
@@ -22727,9 +22770,26 @@ function mapWorkflowRunEstimate(value, definitionId) {
22727
22770
  canAfford: value.can_afford,
22728
22771
  definitionId: value.definition_id,
22729
22772
  definitionVersion: value.definition_version,
22730
- estimatedCost: value.estimated_cost
22773
+ estimatedCost: value.estimated_cost,
22774
+ executionTimeRanges: (value.execution_time_ranges ?? []).flatMap((item) => {
22775
+ const range = item.range;
22776
+ if (!range || !isNonNegativeFinite(range.minSeconds) || !isNonNegativeFinite(range.maxSeconds) || !isNonNegativeFinite(range.medianSeconds) || !isNonNegativeFinite(range.sampleCount)) {
22777
+ return [];
22778
+ }
22779
+ return [
22780
+ {
22781
+ maxSeconds: range.maxSeconds,
22782
+ medianSeconds: range.medianSeconds,
22783
+ minSeconds: range.minSeconds,
22784
+ sampleCount: range.sampleCount
22785
+ }
22786
+ ];
22787
+ })
22731
22788
  };
22732
22789
  }
22790
+ function isNonNegativeFinite(value) {
22791
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
22792
+ }
22733
22793
  function mapWorkflowDefinition(value) {
22734
22794
  if (!value.id || !value.name || typeof value.version !== "number") {
22735
22795
  throw new Error("Workflow Runtime returned an invalid Definition.");
@@ -22903,7 +22963,8 @@ var CanvasSession = class {
22903
22963
  projectionRevision = 0;
22904
22964
  actorId = null;
22905
22965
  health;
22906
- async connect(displayName, onState) {
22966
+ async connect(clientName, onState) {
22967
+ const normalizedClientName = normalizeCanvasClientName(clientName);
22907
22968
  this.closeContext();
22908
22969
  this.transition("authenticating", onState);
22909
22970
  this.health.connectionAttempts += 1;
@@ -22927,7 +22988,10 @@ var CanvasSession = class {
22927
22988
  });
22928
22989
  this.document = document2;
22929
22990
  try {
22930
- document2.setIdentity({ accountId: actor.accountId, displayName });
22991
+ document2.setIdentity({
22992
+ accountId: actor.accountId,
22993
+ displayName: formatCanvasAwarenessName(actor.displayName, normalizedClientName)
22994
+ });
22931
22995
  this.transition("hydrating", onState);
22932
22996
  const foundationData = this.dependencies.foundationData ?? createAz8FoundationDataSystem(this.config);
22933
22997
  const projectContent = this.dependencies.projectContent ?? createAz8ProjectContentSystem(this.config);
@@ -23037,8 +23101,7 @@ var CanvasSession = class {
23037
23101
  }
23038
23102
  publishAttentionBounds(bounds) {
23039
23103
  this.requireDocument().setAttention({
23040
- cursor: { x: bounds.x + bounds.width + 24, y: bounds.y + bounds.height / 2 },
23041
- selection: bounds
23104
+ cursor: { x: bounds.x + bounds.width + 24, y: bounds.y + bounds.height / 2 }
23042
23105
  });
23043
23106
  }
23044
23107
  closeContext() {
@@ -23379,18 +23442,20 @@ function requirePositiveInteger(value, field) {
23379
23442
 
23380
23443
  // src/canvas-protocol-server.ts
23381
23444
  var CanvasProtocolServer = class {
23382
- constructor(config, projectId, output, session) {
23445
+ constructor(config, projectId, output, session, options = {}) {
23383
23446
  this.projectId = projectId;
23384
23447
  this.output = output;
23448
+ this.options = options;
23385
23449
  this.client = new CanvasClient(config, projectId, session);
23386
23450
  }
23387
23451
  projectId;
23388
23452
  output;
23453
+ options;
23389
23454
  client;
23390
23455
  initialized = false;
23391
23456
  shuttingDown = false;
23392
23457
  shutdownFinalized = false;
23393
- displayName = "AZ8 CLI";
23458
+ clientName = "cli";
23394
23459
  observationPreset = "compact";
23395
23460
  observationInclude = /* @__PURE__ */ new Set();
23396
23461
  recordedRequests = /* @__PURE__ */ new Map();
@@ -23533,8 +23598,11 @@ var CanvasProtocolServer = class {
23533
23598
  "initialize projectId does not match canvas open."
23534
23599
  );
23535
23600
  }
23601
+ const clientName = normalizeCanvasClientName(
23602
+ this.options.clientName ?? request.payload.client?.name
23603
+ );
23536
23604
  this.initialized = true;
23537
- this.displayName = request.payload.client?.name ?? "AZ8 CLI";
23605
+ this.clientName = clientName;
23538
23606
  this.observationPreset = request.payload.observationPreset ?? "compact";
23539
23607
  this.unsubscribeObservation = this.session.subscribe((observation) => {
23540
23608
  const category = observation.kind === "workflow" ? "workflow" : "canvas";
@@ -23558,7 +23626,7 @@ var CanvasProtocolServer = class {
23558
23626
  }
23559
23627
  async connect(requestId) {
23560
23628
  try {
23561
- await this.client.connect(this.displayName, (state) => {
23629
+ await this.client.connect(this.clientName, (state) => {
23562
23630
  void this.output.emitMandatory({ payload: { state }, requestId, type: "lifecycle" });
23563
23631
  });
23564
23632
  } catch (error) {
@@ -23936,7 +24004,7 @@ async function runCanvasOneShot(config, command, dependencies = {}) {
23936
24004
  try {
23937
24005
  assertProcessIndependent(command);
23938
24006
  client = dependencies.clientFactory?.(config, command.projectId) ?? new CanvasClient(config, command.projectId);
23939
- await client.connect(command.clientName ?? "AZ8 CLI one-shot");
24007
+ await client.connect(normalizeCanvasClientName(command.clientName));
23940
24008
  if (command.kind === "query") {
23941
24009
  return {
23942
24010
  exitCode: 0,
@@ -24159,7 +24227,7 @@ async function runCanvasMediaDownload(config, command, dependencies = {}) {
24159
24227
  try {
24160
24228
  client = dependencies.mediaDownloadClientFactory?.(config, command.projectId) ?? new CanvasClient(config, command.projectId);
24161
24229
  try {
24162
- await client.connect(command.clientName ?? "AZ8 CLI media download");
24230
+ await client.connect(normalizeCanvasClientName(command.clientName));
24163
24231
  } catch {
24164
24232
  throw new MediaDownloadError(
24165
24233
  "canvas-connection-failed",
@@ -25029,7 +25097,7 @@ function projectHelpOutput() {
25029
25097
  function canvasHelpOutput() {
25030
25098
  return [
25031
25099
  "Canvas:",
25032
- " az8 canvas open <project-id> --write --format ndjson",
25100
+ " az8 canvas open <project-id> --write --format ndjson [--client-name <suffix>]",
25033
25101
  " az8 canvas inspect <project-id> Print the latest persisted snapshot as JSON",
25034
25102
  " az8 canvas upload <project-id> --file <path> --idempotency-key <key>",
25035
25103
  " az8 canvas download <project-id> (--view-node-id <id> | --core-node-id <id>) --output <path>",
@@ -25042,7 +25110,7 @@ function canvasHelpOutput() {
25042
25110
  " --input-json <json> Read one inline JSON object",
25043
25111
  " --request-id <id> Set the Operation Receipt request ID",
25044
25112
  " --timeout-ms <ms> Set the operation acknowledgement timeout",
25045
- " --client-name <name> Set the ephemeral awareness display name",
25113
+ " --client-name <suffix> Set the parenthesized awareness client suffix",
25046
25114
  "",
25047
25115
  "One-shot query options:",
25048
25116
  " --view-node-id <id> Select one View Node for canvas.detail",
@@ -25235,7 +25303,15 @@ async function runPersistentCanvas(args2) {
25235
25303
  });
25236
25304
  try {
25237
25305
  const config = await resolveCliConfig(args2, {});
25238
- server = new CanvasProtocolServer(config, projectId, output);
25306
+ const requestedClientName = readOption3(args2, "--client-name");
25307
+ const clientName = requestedClientName ? normalizeCanvasClientName(requestedClientName) : void 0;
25308
+ server = new CanvasProtocolServer(
25309
+ config,
25310
+ projectId,
25311
+ output,
25312
+ void 0,
25313
+ clientName ? { clientName } : {}
25314
+ );
25239
25315
  const shutdownOnSignal = (signal) => {
25240
25316
  void server?.shutdownFromControl(signal.toLowerCase()).finally(() => process.stdin.destroy());
25241
25317
  };
@@ -25252,7 +25328,10 @@ async function runPersistentCanvas(args2) {
25252
25328
  }
25253
25329
  function readOption3(args2, name) {
25254
25330
  const index = args2.indexOf(name);
25255
- return index >= 0 ? args2[index + 1] : void 0;
25331
+ if (index < 0) return void 0;
25332
+ const value = args2[index + 1];
25333
+ if (!value || value.startsWith("--")) throw new Error(`${name} requires a value.`);
25334
+ return value;
25256
25335
  }
25257
25336
  function safeDiagnostic(error) {
25258
25337
  const message = error instanceof Error ? error.message : "Persistent Canvas client failed.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everfir/az8-cli",
3
- "version": "0.2.0-preview.4",
3
+ "version": "0.2.0",
4
4
  "description": "Semantic Project Canvas client for AZ8 Studio agents.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -16,20 +16,21 @@
16
16
  "main": "./dist/main.js",
17
17
  "publishConfig": {
18
18
  "access": "public",
19
- "tag": "preview"
19
+ "tag": "latest"
20
20
  },
21
21
  "scripts": {
22
- "build": "rm -rf dist && esbuild src/main.ts --bundle --platform=node --format=esm --target=node20 --banner:js='import { createRequire } from \"node:module\"; const require = createRequire(import.meta.url);' --outfile=dist/main.js",
22
+ "build": "pnpm build:dependencies && rm -rf dist && esbuild src/main.ts --bundle --platform=node --format=esm --target=node20 --banner:js='import { createRequire } from \"node:module\"; const require = createRequire(import.meta.url);' --outfile=dist/main.js",
23
+ "build:dependencies": "pnpm --filter @everfir/az8-cli^... build",
23
24
  "dev": "tsx src/main.ts",
24
25
  "start": "node dist/main.js",
25
26
  "typecheck": "tsc -p tsconfig.json --noEmit",
26
27
  "lint": "biome check .",
27
28
  "prepack": "pnpm build",
28
29
  "prepublishOnly": "pnpm release:check",
29
- "release:check": "pnpm test && pnpm typecheck && node scripts/verify-package.js",
30
+ "release:check": "pnpm build:dependencies && pnpm test && pnpm typecheck && node scripts/verify-package.js",
30
31
  "release:pack": "node scripts/verify-package.js --output-dir ../../tmp/az8-cli-release --keep-install",
31
32
  "release:smoke": "node scripts/smoke-test.js",
32
- "test": "tsx --tsconfig tsconfig.test.json --test src/**/*.test.ts"
33
+ "test": "tsx --tsconfig tsconfig.test.json --test src/**/*.test.ts && node --test scripts/*.test.js"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^25.5.0",
@@ -46,8 +46,10 @@ capabilities instead of guessing operation names or input shapes.
46
46
  outcome, revisions, and optional usage. It is not Canvas History.
47
47
  - **History** contains reversible local interactions. Only Operations advertised as `undoable`
48
48
  participate. A generation start is a History barrier and is not undone by `undo`.
49
- - **Awareness** is ephemeral presence, client name, and Attention Cursor information. It never
50
- grants authority, changes persisted authorship, or proves a business result.
49
+ - **Awareness** is ephemeral presence, client suffix, and Attention Cursor information. The visible
50
+ name is always `<authenticated display name> (<client suffix>)`; the default suffix is `cli`.
51
+ CLI awareness does not publish selection bounds. Awareness never grants authority, changes
52
+ persisted authorship, or proves a business result.
51
53
  - **Actor** is the authenticated account from `AZ8_TOKEN`. A client or awareness name cannot
52
54
  impersonate another author.
53
55
 
@@ -131,6 +133,10 @@ and Operations, observe concurrent changes, and shut it down only after the Canv
131
133
  az8 canvas open <project-id> --write --format ndjson
132
134
  ```
133
135
 
136
+ Use `--client-name <suffix>` only when a more specific CLI presence label helps collaborators. The
137
+ CLI owns the surrounding parentheses, rejects parentheses in the supplied suffix, and always keeps
138
+ the authenticated user display name first.
139
+
134
140
  1. Continuously drain stdout; it is an ordered NDJSON protocol stream and may apply backpressure.
135
141
  2. Read `hello`, select protocol `1.0`, then send `initialize` for the same Project ID.
136
142
  3. Wait for `ready` before any query or Operation.
@@ -144,6 +150,24 @@ az8 canvas open <project-id> --write --format ndjson
144
150
  Protocol truth is written to stdout. stderr contains diagnostics only. Unknown optional fields are
145
151
  additive; ignore them. Do not treat a process exit code as the result of the last persistent request.
146
152
 
153
+ Treat the persistent process lifetime and each request lifetime separately:
154
+
155
+ - Keep the process running between requests. Decide request completion only from the matching
156
+ `requestId` and its `requestCompleted` or `requestFailed` frame; never wait for process exit.
157
+ - Treat `requestAccepted` as admission only. For a write, wait for its terminal Receipt before
158
+ issuing the next query or Operation.
159
+ - Continuously drain and parse stdout, but do not repeatedly poll an idle process merely to make it
160
+ produce output. No new frame is a normal healthy state when no lifecycle, request, Canvas, or
161
+ Workflow fact changed.
162
+ - Do not interpret a shell/agent-host read window ending with no output (for example, a 30-second
163
+ terminal yield) as CLI latency, request timeout, or connection failure. Check request terminal
164
+ frames and lifecycle/health facts instead.
165
+ - Keep one connected client for related work. Reopening one-shot clients repeatedly pays connection,
166
+ synchronization, and hydration cost each time and loses local History, Receipts, and presence.
167
+ - Remember that the current persistent protocol processes requests serially. A long
168
+ `waitWorkflowRun` occupies the request lane until the Run becomes terminal or its observation
169
+ window ends; submit it only when no other request needs that lane.
170
+
147
171
  Use one-shot only for:
148
172
 
149
173
  - an isolated read when no Canvas write is planned;
@@ -195,10 +219,15 @@ Generation is an explicit sequence, never one transaction:
195
219
  current facts. Stop after the first non-success.
196
220
  5. `startGeneration` succeeds when the Workflow Run is accepted and immediate Canvas effects are
197
221
  acknowledged. This completes the start interaction; it does not mean generation finished.
198
- 6. Retain the Workflow Run ID. Use `waitWorkflowRun` when waiting is useful, or `getWorkflowRun`
199
- later. An observation-window timeout while the authoritative Run remains pending/running is an
200
- ordinary read outcome and does not cancel the Run or take a healthy context offline.
201
- 7. On terminal success, query the target detail and resolve its current output Core Node through
222
+ 6. Retain the Workflow Run ID. After the successful start Receipt, continue any work that does not
223
+ require the generated output. Prefer `workflowChanged` observations while the persistent client
224
+ is active, and use `getWorkflowRun` at a meaningful later checkpoint.
225
+ 7. Use `waitWorkflowRun` only when the next step actually requires terminal output and the current
226
+ request lane can remain dedicated to waiting. Choose one intentional observation window; do not
227
+ emulate polling with repeated short waits. An observation-window timeout while the authoritative
228
+ Run remains pending/running is an ordinary read outcome and does not cancel the Run or take a
229
+ healthy context offline.
230
+ 8. On terminal success, query the target detail and resolve its current output Core Node through
202
231
  Project Content before downloading or placing it elsewhere.
203
232
 
204
233
  Workflow Definitions change over time. Query `workflowDefinitions.summary`, then
@@ -207,7 +236,9 @@ configuration option, modality, cost, or media compatibility from its display na
207
236
 
208
237
  The start Receipt may report `usage.credits.status` as `estimated`, `not-consumed`, or `unknown`.
209
238
  Estimated cost is informational, not final charged usage. There is no second confirmation. Never
210
- claim actual credit consumption from an estimate.
239
+ claim actual credit consumption from an estimate. When timing data is available, `startGeneration`
240
+ also records the same median and maximum completion timestamps Web uses to display expected duration;
241
+ these timestamps remain estimates and do not change the Run's completion boundary.
211
242
 
212
243
  ## Media
213
244
 
@@ -285,8 +316,8 @@ Never:
285
316
  or protocol payloads;
286
317
  - inspect, print, or edit an operator-provisioned credential source merely to check whether it
287
318
  exists; authenticate with a bounded read instead;
288
- - use awareness client names to impersonate persisted authors or treat Attention Cursor position as
289
- authority;
319
+ - use awareness client suffixes to impersonate persisted authors, supply parentheses to obscure the
320
+ authenticated-name boundary, or treat Attention Cursor position as authority;
290
321
  - use one-shot as if it retained History, Receipts, replay state, observations, or presence;
291
322
  - attempt Asset membership, arbitrary Core Node creation, 3D World editing, unsupported
292
323
  Decoration, alignment, or automatic layout through lower-level access;