@ze-norm/cli 0.12.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ import { CliError } from "../util/errors.js";
2
+ import { PRODUCTION_API_URL, PRODUCTION_CLERK_CLIENT_ID, PRODUCTION_CLERK_ISSUER, } from "./defaults.js";
3
+ const PROD_ENVIRONMENT = {
4
+ name: "prod",
5
+ apiUrl: PRODUCTION_API_URL,
6
+ clerkIssuer: PRODUCTION_CLERK_ISSUER,
7
+ clerkClientId: PRODUCTION_CLERK_CLIENT_ID,
8
+ };
9
+ export const ZENORM_CLI_ENV_VAR = "ZENORM_CLI";
10
+ const ENV_NAMES = ["prod", "dev", "local"];
11
+ /**
12
+ * Normalize a raw `ZENORM_CLI` value to a canonical env name. Accepts the bare
13
+ * name (`prod`/`dev`/`local`), `production`, and each name with a `-zenorm`
14
+ * suffix (constructed here so the suffixed literal is never a static token).
15
+ */
16
+ function parseEnvName(raw) {
17
+ const key = raw.trim().toLowerCase();
18
+ if (key === "production")
19
+ return "prod";
20
+ for (const name of ENV_NAMES) {
21
+ if (key === name || key === `${name}-zenorm`)
22
+ return name;
23
+ }
24
+ return null;
25
+ }
26
+ /**
27
+ * Build a non-prod environment from the endpoint env vars injected by the
28
+ * repo launchers. Fails loud if they are missing — that means the public
29
+ * `zenorm` was pointed at an internal stack without the launcher that carries
30
+ * its (non-shipped) endpoints, and we must not silently fall back to prod.
31
+ */
32
+ function environmentFromInjectedVars(name) {
33
+ const apiUrl = process.env["ZENORM_API_URL"];
34
+ const clerkIssuer = process.env["ZENORM_CLERK_ISSUER"];
35
+ const clerkClientId = process.env["ZENORM_CLERK_CLIENT_ID"];
36
+ if (!apiUrl || !clerkIssuer || !clerkClientId) {
37
+ const shim = `${name}${"-zenorm"}`;
38
+ throw new CliError(`${ZENORM_CLI_ENV_VAR}=${name} needs the ${name} endpoints, which are not ` +
39
+ `bundled in the public CLI. Launch it with \`${shim}\` (from the repo), ` +
40
+ `or set ZENORM_API_URL, ZENORM_CLERK_ISSUER, and ZENORM_CLERK_CLIENT_ID.`);
41
+ }
42
+ return { name, apiUrl, clerkIssuer, clerkClientId };
43
+ }
44
+ /**
45
+ * Resolve the active CLI environment from `ZENORM_CLI`.
46
+ *
47
+ * Unset → production (the public default). A set-but-unrecognized value is a
48
+ * loud error rather than a silent fallback: a typo like `ZENORM_CLI=prd` must
49
+ * not quietly send `zenorm` commands at the wrong stack.
50
+ */
51
+ export function resolveEnvironment() {
52
+ const raw = process.env[ZENORM_CLI_ENV_VAR];
53
+ if (raw === undefined || raw.trim() === "") {
54
+ return PROD_ENVIRONMENT;
55
+ }
56
+ const name = parseEnvName(raw);
57
+ if (!name) {
58
+ const allowed = ENV_NAMES.join(", ");
59
+ throw new CliError(`Invalid ${ZENORM_CLI_ENV_VAR}=${JSON.stringify(raw)}. ` +
60
+ `Expected one of: ${allowed} (or a "-zenorm" suffixed form).`);
61
+ }
62
+ if (name === "prod")
63
+ return PROD_ENVIRONMENT;
64
+ return environmentFromInjectedVars(name);
65
+ }
@@ -1,6 +1,16 @@
1
1
  export interface ZenormConfig {
2
2
  org?: string;
3
3
  apiUrl?: string;
4
+ /**
5
+ * Opt into coding-agent mode for local `zenorm work` runs: instead of running
6
+ * each task headlessly, launch the active coding agent (the one named by
7
+ * `--agent`) as a LIVE interactive session seeded with the spec handoff. The
8
+ * operator works with the agent, then closes it to release the worker and
9
+ * continue to the next task. Off by default; the `--interactive` flag
10
+ * overrides this. Requires a usable local TTY at run time (fails loud
11
+ * otherwise — see `interactive-agent.ts`).
12
+ */
13
+ interactive?: boolean;
4
14
  }
5
15
  export declare function validateConfig(raw: unknown): ZenormConfig;
6
16
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAuBzD"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CA8BzD"}
@@ -17,5 +17,11 @@ export function validateConfig(raw) {
17
17
  }
18
18
  config.apiUrl = obj["apiUrl"];
19
19
  }
20
+ if ("interactive" in obj) {
21
+ if (typeof obj["interactive"] !== "boolean") {
22
+ throw new CliError('.zenorm.json: "interactive" must be a boolean');
23
+ }
24
+ config.interactive = obj["interactive"];
25
+ }
20
26
  return config;
21
27
  }
@@ -21,4 +21,20 @@ export type SpecBlock = {
21
21
  * the agent sees a clean "## goals", "## acceptance_criterion" structure.
22
22
  */
23
23
  export declare function blocksToMarkdown(blocks: SpecBlock[]): string;
24
+ export type AttachmentRecord = {
25
+ id: string;
26
+ fileName: string;
27
+ fileSize: number;
28
+ mimeType: string;
29
+ status: string;
30
+ };
31
+ /**
32
+ * Render the spec's ready attachments as an "## Attachments" markdown section
33
+ * for the worker work context. Lists fileName — mimeType — pretty size only;
34
+ * the file CONTENT/bytes are never inlined (binary can't be, and no AC needs
35
+ * download). Callers must pass ONLY ready attachments — the server is the gate
36
+ * (the `?status=ready` filter), this renderer does not re-filter. Returns an
37
+ * empty string when there are none so `pull` can omit the section.
38
+ */
39
+ export declare function attachmentsToMarkdown(attachments: AttachmentRecord[]): string;
24
40
  //# sourceMappingURL=markdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/util/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAmB1D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAqB1E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CA+B5D"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/util/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAmB1D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAqB1E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CA+B5D;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAmBF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAO7E"}
@@ -77,6 +77,40 @@ export function blocksToMarkdown(blocks) {
77
77
  }
78
78
  return out.join("\n").trimEnd();
79
79
  }
80
+ /**
81
+ * Render a human-readable byte size (e.g. 1536 → "1.5 KB"). Used for the
82
+ * attachments listing only — purely cosmetic.
83
+ */
84
+ function prettySize(bytes) {
85
+ if (!Number.isFinite(bytes) || bytes < 0)
86
+ return `${bytes} B`;
87
+ const units = ["B", "KB", "MB", "GB"];
88
+ let value = bytes;
89
+ let unit = 0;
90
+ while (value >= 1024 && unit < units.length - 1) {
91
+ value /= 1024;
92
+ unit += 1;
93
+ }
94
+ const rounded = unit === 0 ? value : Math.round(value * 10) / 10;
95
+ return `${rounded} ${units[unit]}`;
96
+ }
97
+ /**
98
+ * Render the spec's ready attachments as an "## Attachments" markdown section
99
+ * for the worker work context. Lists fileName — mimeType — pretty size only;
100
+ * the file CONTENT/bytes are never inlined (binary can't be, and no AC needs
101
+ * download). Callers must pass ONLY ready attachments — the server is the gate
102
+ * (the `?status=ready` filter), this renderer does not re-filter. Returns an
103
+ * empty string when there are none so `pull` can omit the section.
104
+ */
105
+ export function attachmentsToMarkdown(attachments) {
106
+ if (attachments.length === 0)
107
+ return "";
108
+ const out = ["## Attachments", ""];
109
+ for (const a of attachments) {
110
+ out.push(`- ${a.fileName} — ${a.mimeType} — ${prettySize(a.fileSize)}`);
111
+ }
112
+ return out.join("\n");
113
+ }
80
114
  function humanizeType(type) {
81
115
  return type.split("_").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
82
116
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ze-norm/cli",
3
3
  "private": false,
4
- "version": "0.12.0",
4
+ "version": "0.14.1",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "type": "module",
7
7
  "repository": {