@ze-norm/cli 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,10 +4,14 @@ Public command-line client for ZeNorm.
4
4
 
5
5
  ```sh
6
6
  npm install -g @ze-norm/cli
7
- zenorm login
8
- zenorm install-skills claude-code
7
+ zenorm setup claude-code
9
8
  ```
10
9
 
10
+ `zenorm setup <agent>` is the one-command onboarding path: it logs you in (or
11
+ skips that if you already are), installs the bundled ZeNorm skills for the agent
12
+ you name, and prints your next steps. Use `zenorm login` and
13
+ `zenorm install-skills` directly if you want to run those steps separately.
14
+
11
15
  The CLI includes the ZeNorm agent skills in the npm package. `zenorm install-skills`
12
16
  installs those bundled skills through the `skills` package; it does not require
13
17
  a separate skills repository.
@@ -1,2 +1,30 @@
1
+ /** Exported so tests can assert every advertised slug is one the skills package accepts. */
2
+ export declare const INSTALL_SKILLS_USAGE = "Usage:\n zenorm install-skills <agent|all|*> [--project|--global]\n\nExamples:\n zenorm install-skills claude-code\n zenorm install-skills codex --project\n zenorm install-skills codex --project --force\n zenorm install-skills '*' --project\n\nAgent names are the slugs supported by the skills package, such as\nclaude-code, codex, cursor, opencode, gemini-cli, github-copilot, windsurf,\nqwen-code, kiro-cli, cline, roo, and more. An unknown slug is rejected with the\nfull list of valid agents.";
3
+ export declare function normalizeAgent(raw: string): string;
4
+ /**
5
+ * Reject an agent slug the skills package does not know, before doing any work.
6
+ *
7
+ * The skills package keeps its agent registry private (no export, no
8
+ * list-agents subcommand), so the package itself is the only authority on which
9
+ * slugs are valid. `skills list --agent <slug>` consults that registry, is
10
+ * read-only, and exits non-zero with `Invalid agents: ... / Valid agents: ...`.
11
+ * Running it as a precheck means a typo fails in milliseconds with the real
12
+ * list, rather than after a browser sign-in round-trip.
13
+ */
14
+ export declare function assertKnownAgent(agent: string): void;
15
+ export interface InstallSkillsOptions {
16
+ /** Install into the current project rather than the global agent directories. */
17
+ project: boolean;
18
+ /** Overwrite ZeNorm skills that are already installed for this agent. */
19
+ force: boolean;
20
+ }
21
+ /**
22
+ * Install the bundled ZeNorm skills for one skills-package agent slug.
23
+ *
24
+ * The shared boundary behind both `zenorm install-skills <agent>` and
25
+ * `zenorm setup <agent>` — the slug is validated here, so a missing/unknown
26
+ * agent fails loud in either entry point rather than defaulting to anything.
27
+ */
28
+ export declare function installSkillsForAgent(requestedAgent: string, opts: InstallSkillsOptions): void;
1
29
  export declare function installSkillsCommand(args: string[]): Promise<void>;
2
30
  //# sourceMappingURL=install-skills.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install-skills.d.ts","sourceRoot":"","sources":["../../src/commands/install-skills.ts"],"names":[],"mappings":"AAqOA,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCxE"}
1
+ {"version":3,"file":"install-skills.d.ts","sourceRoot":"","sources":["../../src/commands/install-skills.ts"],"names":[],"mappings":"AAiBA,4FAA4F;AAC5F,eAAO,MAAM,oBAAoB,0fAYN,CAAC;AAwC5B,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMlD;AAkID;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMpD;AAgFD,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,oBAAoB,GACzB,IAAI,CAIN;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CxE"}
@@ -3,11 +3,12 @@ import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, } from "n
3
3
  import { createRequire } from "node:module";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join, parse, resolve } from "node:path";
6
- import { parseArgs } from "node:util";
6
+ import { parseArgs } from "../util/parse-args.js";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { CliError } from "../util/errors.js";
9
9
  import { log } from "../util/logger.js";
10
- const USAGE = `Usage:
10
+ /** Exported so tests can assert every advertised slug is one the skills package accepts. */
11
+ export const INSTALL_SKILLS_USAGE = `Usage:
11
12
  zenorm install-skills <agent|all|*> [--project|--global]
12
13
 
13
14
  Examples:
@@ -18,7 +19,8 @@ Examples:
18
19
 
19
20
  Agent names are the slugs supported by the skills package, such as
20
21
  claude-code, codex, cursor, opencode, gemini-cli, github-copilot, windsurf,
21
- qwen, kiro, cline, roo, and more.`;
22
+ qwen-code, kiro-cli, cline, roo, and more. An unknown slug is rejected with the
23
+ full list of valid agents.`;
22
24
  const AGENT_SLUG_RE = /^[a-z0-9][a-z0-9-]*$|^\*$/;
23
25
  const require = createRequire(import.meta.url);
24
26
  function getBundledSkillsRoot() {
@@ -47,10 +49,10 @@ function getSkillsCliPath() {
47
49
  throw new CliError("Could not find the bundled `skills` package. Reinstall @ze-norm/cli and try again.");
48
50
  }
49
51
  }
50
- function normalizeAgent(raw) {
52
+ export function normalizeAgent(raw) {
51
53
  const normalized = raw.toLowerCase() === "all" ? "*" : raw.toLowerCase();
52
54
  if (!AGENT_SLUG_RE.test(normalized)) {
53
- throw new CliError(`Invalid agent "${raw}".\n\n${USAGE}`);
55
+ throw new CliError(`Invalid agent "${raw}".\n\n${INSTALL_SKILLS_USAGE}`);
54
56
  }
55
57
  return normalized;
56
58
  }
@@ -108,23 +110,83 @@ function skillBelongsToAgent(skill, agent) {
108
110
  const wanted = normalizeAgentLabel(agent);
109
111
  return skill.agents.some((label) => typeof label === "string" && normalizeAgentLabel(label) === wanted);
110
112
  }
111
- function listInstalledSkills(agent, project) {
112
- const scopeArgs = project ? [] : ["--global"];
113
- const agentArgs = agent === "*" ? [] : ["--agent", agent];
114
- const args = [getSkillsCliPath(), "list", "--json", ...agentArgs, ...scopeArgs];
113
+ /** Strip ANSI escapes so the skills CLI's colored output reads cleanly when we re-emit it. */
114
+ function stripAnsi(text) {
115
+ // eslint-disable-next-line no-control-regex
116
+ return text.replace(/\u001b\[[0-9;?]*[a-zA-Z]/g, "");
117
+ }
118
+ /**
119
+ * The stdout/stderr a failed `execFileSync` captured, as readable text.
120
+ *
121
+ * Node attaches the captured pipes to the thrown error. Without this the real
122
+ * diagnosis — e.g. `Invalid agents: qwen / Valid agents: ...` — is discarded and
123
+ * the user is left with a generic message that names the wrong problem.
124
+ */
125
+ function capturedOutput(err) {
126
+ if (typeof err !== "object" || err === null)
127
+ return "";
128
+ const { stderr, stdout } = err;
129
+ return [stderr, stdout]
130
+ .map((stream) => typeof stream === "string"
131
+ ? stream
132
+ : Buffer.isBuffer(stream)
133
+ ? stream.toString("utf-8")
134
+ : "")
135
+ .map((text) => stripAnsi(text).trim())
136
+ .filter((text) => text.length > 0)
137
+ .join("\n");
138
+ }
139
+ /**
140
+ * Run the bundled skills CLI, surfacing its own output when it fails.
141
+ *
142
+ * Fail loud: the skills CLI already explains precisely what went wrong (unknown
143
+ * agent slug, and the full list of valid ones). Swallowing that and substituting
144
+ * a generic sentence masks the error, so its output is always re-emitted.
145
+ */
146
+ function runSkillsCli(args, summary) {
115
147
  try {
116
- const stdout = execFileSync(process.execPath, args, {
148
+ return execFileSync(process.execPath, [getSkillsCliPath(), ...args], {
117
149
  encoding: "utf-8",
118
150
  stdio: ["ignore", "pipe", "pipe"],
119
151
  });
152
+ }
153
+ catch (err) {
154
+ const detail = capturedOutput(err);
155
+ log.debug("skills cli failed", {
156
+ args,
157
+ error: err instanceof Error ? err.message : String(err),
158
+ });
159
+ throw new CliError(detail.length > 0 ? `${summary}\n\n${detail}` : summary);
160
+ }
161
+ }
162
+ /**
163
+ * Reject an agent slug the skills package does not know, before doing any work.
164
+ *
165
+ * The skills package keeps its agent registry private (no export, no
166
+ * list-agents subcommand), so the package itself is the only authority on which
167
+ * slugs are valid. `skills list --agent <slug>` consults that registry, is
168
+ * read-only, and exits non-zero with `Invalid agents: ... / Valid agents: ...`.
169
+ * Running it as a precheck means a typo fails in milliseconds with the real
170
+ * list, rather than after a browser sign-in round-trip.
171
+ */
172
+ export function assertKnownAgent(agent) {
173
+ if (agent === "*")
174
+ return;
175
+ runSkillsCli(["list", "--json", "--agent", agent, "--global"], `Unknown agent "${agent}".`);
176
+ }
177
+ function listInstalledSkills(agent, project) {
178
+ const scopeArgs = project ? [] : ["--global"];
179
+ const agentArgs = agent === "*" ? [] : ["--agent", agent];
180
+ const stdout = runSkillsCli(["list", "--json", ...agentArgs, ...scopeArgs], "Could not inspect existing installed skills before installing.");
181
+ try {
120
182
  const parsed = JSON.parse(stdout);
121
183
  return Array.isArray(parsed) ? parsed : [];
122
184
  }
123
185
  catch (err) {
124
- log.debug("skills list failed", {
186
+ log.debug("skills list returned unparsable JSON", {
125
187
  error: err instanceof Error ? err.message : String(err),
126
188
  });
127
- throw new CliError("Could not inspect existing installed skills before installing.");
189
+ throw new CliError(`Could not parse the skills package's list output.\n\n${stripAnsi(stdout).trim()}`);
128
190
  }
129
191
  }
130
192
  function assertNoInstalledZeNormSkills(agent, project, force) {
@@ -170,6 +232,18 @@ function runSkillsInstaller(agent, project, force) {
170
232
  throw new CliError("Failed to install ZeNorm skills. Check the output above for details.");
171
233
  }
172
234
  }
235
+ /**
236
+ * Install the bundled ZeNorm skills for one skills-package agent slug.
237
+ *
238
+ * The shared boundary behind both `zenorm install-skills <agent>` and
239
+ * `zenorm setup <agent>` — the slug is validated here, so a missing/unknown
240
+ * agent fails loud in either entry point rather than defaulting to anything.
241
+ */
242
+ export function installSkillsForAgent(requestedAgent, opts) {
243
+ const agent = normalizeAgent(requestedAgent);
244
+ assertKnownAgent(agent);
245
+ runSkillsInstaller(agent, opts.project, opts.force);
246
+ }
173
247
  export async function installSkillsCommand(args) {
174
248
  const { positionals, values } = parseArgs({
175
249
  args,
@@ -181,7 +255,7 @@ export async function installSkillsCommand(args) {
181
255
  "target-dir": { type: "string" },
182
256
  },
183
257
  strict: true,
184
- });
258
+ }, "install-skills");
185
259
  const targetDir = values["target-dir"];
186
260
  if (targetDir !== undefined) {
187
261
  if (positionals.length > 0) {
@@ -195,14 +269,16 @@ export async function installSkillsCommand(args) {
195
269
  }
196
270
  const requestedAgent = positionals[0];
197
271
  if (!requestedAgent) {
198
- throw new CliError(`Missing agent.\n\n${USAGE}`);
272
+ throw new CliError(`Missing agent.\n\n${INSTALL_SKILLS_USAGE}`);
199
273
  }
200
274
  if (positionals.length > 1) {
201
- throw new CliError(`Unexpected extra argument: ${positionals[1]}\n\n${USAGE}`);
275
+ throw new CliError(`Unexpected extra argument: ${positionals[1]}\n\n${INSTALL_SKILLS_USAGE}`);
202
276
  }
203
277
  if (values.global && values.project) {
204
278
  throw new CliError("Choose only one scope: --global or --project.");
205
279
  }
206
- const agent = normalizeAgent(requestedAgent);
207
- runSkillsInstaller(agent, values.project === true, values.force === true);
280
+ installSkillsForAgent(requestedAgent, {
281
+ project: values.project === true,
282
+ force: values.force === true,
283
+ });
208
284
  }
@@ -1,2 +1,28 @@
1
+ /**
2
+ * How an `ensureAuthenticated()` call resolved. `setup` uses this to report
3
+ * "already logged in, skipping" vs. "logged you in just now" honestly, without
4
+ * re-running the browser flow.
5
+ */
6
+ export type AuthMode = "local-dev-bypass" | "already-authenticated" | "logged-in";
7
+ export interface AuthResult {
8
+ mode: AuthMode;
9
+ userId: string;
10
+ orgId: string;
11
+ }
12
+ /**
13
+ * The single shared auth path used by both `zenorm login` and `zenorm setup`.
14
+ *
15
+ * Order of resolution:
16
+ * 1. Local dev-bypass target (localhost + bypass enabled) — no credentials.
17
+ * 2. Existing stored/env credentials that the API still accepts.
18
+ * 3. The real browser device-auth flow.
19
+ *
20
+ * `force` skips step 2 so an explicit `zenorm login` always re-authenticates.
21
+ */
22
+ export declare function ensureAuthenticated(opts: {
23
+ force: boolean;
24
+ }): Promise<AuthResult>;
25
+ /** Print the human-readable result of an `ensureAuthenticated()` call. */
26
+ export declare function reportAuthResult(result: AuthResult): void;
1
27
  export declare function loginCommand(argv: string[]): Promise<void>;
2
28
  //# sourceMappingURL=login.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAMA,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuChE"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,kBAAkB,GAAG,uBAAuB,GAAG,WAAW,CAAC;AAElF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAyBvF;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAiBzD;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBhE"}
@@ -1,24 +1,28 @@
1
- import { parseArgs } from "node:util";
1
+ import { parseArgs } from "../util/parse-args.js";
2
2
  import { runDeviceAuthFlow } from "../auth/device-flow.js";
3
- import { saveCredentials } from "../auth/store.js";
3
+ import { loadCredentials, saveCredentials } from "../auth/store.js";
4
4
  import { ZenormClient } from "../api/client.js";
5
5
  import { log } from "../util/logger.js";
6
- export async function loginCommand(argv) {
7
- const { values } = parseArgs({
8
- args: argv,
9
- options: {
10
- "skip-skills": { type: "boolean", default: false },
11
- },
12
- strict: true,
13
- });
14
- log.info("Logging in to ZeNorm...");
6
+ /**
7
+ * The single shared auth path used by both `zenorm login` and `zenorm setup`.
8
+ *
9
+ * Order of resolution:
10
+ * 1. Local dev-bypass target (localhost + bypass enabled) — no credentials.
11
+ * 2. Existing stored/env credentials that the API still accepts.
12
+ * 3. The real browser device-auth flow.
13
+ *
14
+ * `force` skips step 2 so an explicit `zenorm login` always re-authenticates.
15
+ */
16
+ export async function ensureAuthenticated(opts) {
15
17
  const localCtx = await tryLocalDevelopmentLogin();
16
18
  if (localCtx) {
17
- log.success("Logged in using local development mode.");
18
- log.info(`User: ${localCtx.userId}`);
19
- log.info(`Org: ${localCtx.orgId}`);
20
- log.dim("No credentials were stored; local API requests use development headers.");
21
- return;
19
+ return { mode: "local-dev-bypass", userId: localCtx.userId, orgId: localCtx.orgId };
20
+ }
21
+ if (!opts.force) {
22
+ const existing = await tryExistingCredentials();
23
+ if (existing) {
24
+ return { mode: "already-authenticated", userId: existing.userId, orgId: existing.orgId };
25
+ }
22
26
  }
23
27
  // Run browser-based authorization flow for hosted environments.
24
28
  const creds = await runDeviceAuthFlow();
@@ -29,12 +33,58 @@ export async function loginCommand(argv) {
29
33
  creds.userId = ctx.userId;
30
34
  // Persist credentials
31
35
  saveCredentials(creds);
32
- log.success("Logged in successfully.");
33
- log.info(`User: ${creds.userId}`);
34
- log.info(`Org: ${creds.orgId}`);
35
- // Skill installation step
36
- if (!values["skip-skills"]) {
37
- log.debug("Skill installation will be added later.");
36
+ return { mode: "logged-in", userId: creds.userId, orgId: creds.orgId };
37
+ }
38
+ /** Print the human-readable result of an `ensureAuthenticated()` call. */
39
+ export function reportAuthResult(result) {
40
+ switch (result.mode) {
41
+ case "local-dev-bypass":
42
+ log.success("Logged in using local development mode.");
43
+ break;
44
+ case "already-authenticated":
45
+ log.success("Already logged in — skipping the browser sign-in.");
46
+ break;
47
+ case "logged-in":
48
+ log.success("Logged in successfully.");
49
+ break;
50
+ }
51
+ log.info(`User: ${result.userId}`);
52
+ log.info(`Org: ${result.orgId}`);
53
+ if (result.mode === "local-dev-bypass") {
54
+ log.dim("No credentials were stored; local API requests use development headers.");
55
+ }
56
+ }
57
+ export async function loginCommand(argv) {
58
+ parseArgs({
59
+ args: argv,
60
+ options: {},
61
+ strict: true,
62
+ }, "login");
63
+ log.info("Logging in to ZeNorm...");
64
+ // `login` is an explicit "sign me in" request, so it always runs the flow
65
+ // rather than short-circuiting on existing credentials.
66
+ const result = await ensureAuthenticated({ force: true });
67
+ reportAuthResult(result);
68
+ if (result.mode !== "local-dev-bypass") {
69
+ log.dim("Next: run `zenorm setup <agent>` to install the ZeNorm skills for your coding agent.");
70
+ }
71
+ }
72
+ /**
73
+ * Probe already-stored (or env-provided) credentials against the API. Returns
74
+ * null when there is nothing stored or the API rejects what we have, so the
75
+ * caller falls through to the real sign-in flow instead of pretending we are
76
+ * authenticated.
77
+ */
78
+ async function tryExistingCredentials() {
79
+ if (!process.env["ZENORM_API_TOKEN"] && !loadCredentials()) {
80
+ return null;
81
+ }
82
+ try {
83
+ const ctx = await new ZenormClient().getAuthContext();
84
+ return { userId: ctx.userId, orgId: ctx.orgId };
85
+ }
86
+ catch {
87
+ return null;
38
88
  }
39
89
  }
40
90
  async function tryLocalDevelopmentLogin() {
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAcA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8F/D"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAcA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiG/D"}
@@ -1,4 +1,4 @@
1
- import { parseArgs } from "node:util";
1
+ import { parseArgs } from "../util/parse-args.js";
2
2
  import { writeFileSync } from "node:fs";
3
3
  import { ZenormClient } from "../api/client.js";
4
4
  import { CliError } from "../util/errors.js";
@@ -12,7 +12,7 @@ export async function pullCommand(argv) {
12
12
  output: { type: "string", short: "o" },
13
13
  },
14
14
  strict: true,
15
- });
15
+ }, "pull");
16
16
  const specRef = positionals[0];
17
17
  if (!specRef) {
18
18
  throw new CliError("Missing spec key or id.\n\nUsage: zenorm pull <spec-key-or-id> [-o <file>]");
@@ -0,0 +1,18 @@
1
+ import { type SupportedAgent } from "./work.js";
2
+ /** Exported so tests can assert every advertised slug is one the skills package accepts. */
3
+ export declare const SETUP_USAGE = "Usage:\n zenorm setup <agent> [--project|--global] [--force]\n\nOne-shot onboarding: signs you in (if you aren't already), installs the bundled\nZeNorm skills for your coding agent, and prints what to do next.\n\nExamples:\n zenorm setup claude-code\n zenorm setup codex --project\n zenorm setup cursor --force\n\nOptions:\n --project Install skills into the current project instead of globally\n --global Install skills into the global agent directories (default)\n --force Reinstall skills even if ZeNorm skills are already present\n\n<agent> is a skills-package slug, such as claude-code, codex, cursor, opencode,\ngemini-cli, github-copilot, windsurf, qwen-code, kiro-cli, cline, or roo. An\nunknown slug is rejected up front with the full list of valid agents.";
4
+ /**
5
+ * Translate a skills-package slug into the `zenorm work --agent` value, or null
6
+ * when that agent has no daemon support. Exported for unit testing.
7
+ */
8
+ export declare function workAgentForSkillsSlug(skillsSlug: string): SupportedAgent | null;
9
+ /**
10
+ * The harness-native invocation each agent uses to trigger the ZeNorm skill
11
+ * from inside its own session. Every agent that can install skills has one;
12
+ * this is what we point non-`work` agents at. Exported for unit testing.
13
+ */
14
+ export declare function harnessInvocation(skillsSlug: string): string;
15
+ /** Print the closing "what to do next" block. Exported for unit testing. */
16
+ export declare function nextStepsLines(skillsSlug: string): string[];
17
+ export declare function setupCommand(argv: string[]): Promise<void>;
18
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAKA,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAElE,4FAA4F;AAC5F,eAAO,MAAM,WAAW,kxBAkB8C,CAAC;AAoBvE;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAEhF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAS5D;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAiC3D;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DhE"}
@@ -0,0 +1,136 @@
1
+ import { parseArgs } from "../util/parse-args.js";
2
+ import { CliError } from "../util/errors.js";
3
+ import { log } from "../util/logger.js";
4
+ import { ensureAuthenticated, reportAuthResult } from "./login.js";
5
+ import { assertKnownAgent, installSkillsForAgent, normalizeAgent } from "./install-skills.js";
6
+ import { SUPPORTED_AGENTS } from "./work.js";
7
+ /** Exported so tests can assert every advertised slug is one the skills package accepts. */
8
+ export const SETUP_USAGE = `Usage:
9
+ zenorm setup <agent> [--project|--global] [--force]
10
+
11
+ One-shot onboarding: signs you in (if you aren't already), installs the bundled
12
+ ZeNorm skills for your coding agent, and prints what to do next.
13
+
14
+ Examples:
15
+ zenorm setup claude-code
16
+ zenorm setup codex --project
17
+ zenorm setup cursor --force
18
+
19
+ Options:
20
+ --project Install skills into the current project instead of globally
21
+ --global Install skills into the global agent directories (default)
22
+ --force Reinstall skills even if ZeNorm skills are already present
23
+
24
+ <agent> is a skills-package slug, such as claude-code, codex, cursor, opencode,
25
+ gemini-cli, github-copilot, windsurf, qwen-code, kiro-cli, cline, or roo. An
26
+ unknown slug is rejected up front with the full list of valid agents.`;
27
+ /**
28
+ * The agent-slug mismatch, made explicit.
29
+ *
30
+ * `install-skills` speaks skills-package slugs (`claude-code`, `cursor`, ...)
31
+ * while `zenorm work --agent` accepts only the two agents it can actually
32
+ * spawn (`claude` | `codex` — see SUPPORTED_AGENTS). They are NOT the same
33
+ * vocabulary: `claude-code` is valid for one and rejected by the other.
34
+ *
35
+ * This map is the single translation point. A skills slug that is absent from
36
+ * it has no `zenorm work` equivalent, and `setup` says so instead of printing a
37
+ * command the CLI would reject. Typed against `SupportedAgent` so a value that
38
+ * `work` does not accept cannot be added here.
39
+ */
40
+ const WORK_AGENT_BY_SKILLS_SLUG = {
41
+ "claude-code": "claude",
42
+ codex: "codex",
43
+ };
44
+ /**
45
+ * Translate a skills-package slug into the `zenorm work --agent` value, or null
46
+ * when that agent has no daemon support. Exported for unit testing.
47
+ */
48
+ export function workAgentForSkillsSlug(skillsSlug) {
49
+ return WORK_AGENT_BY_SKILLS_SLUG[skillsSlug] ?? null;
50
+ }
51
+ /**
52
+ * The harness-native invocation each agent uses to trigger the ZeNorm skill
53
+ * from inside its own session. Every agent that can install skills has one;
54
+ * this is what we point non-`work` agents at. Exported for unit testing.
55
+ */
56
+ export function harnessInvocation(skillsSlug) {
57
+ switch (skillsSlug) {
58
+ case "codex":
59
+ return "$zenorm <SPEC-KEY>";
60
+ case "cursor":
61
+ return "@zenorm <SPEC-KEY>";
62
+ default:
63
+ return "/zenorm <SPEC-KEY>";
64
+ }
65
+ }
66
+ /** Print the closing "what to do next" block. Exported for unit testing. */
67
+ export function nextStepsLines(skillsSlug) {
68
+ const workAgent = workAgentForSkillsSlug(skillsSlug);
69
+ const invocation = harnessInvocation(skillsSlug);
70
+ const lines = [
71
+ "",
72
+ "Next steps:",
73
+ " 1. cd into the git repository you want to work in. Its `origin` remote must",
74
+ " match a repository you have imported into ZeNorm — that is how ZeNorm",
75
+ " knows which tasks belong to this checkout.",
76
+ ];
77
+ if (workAgent) {
78
+ lines.push(` 2. Run the task daemon there: zenorm work --agent ${workAgent}`, ` It claims ready tasks for that repo and hands each one to ${skillsSlug}.`, ` 3. Or, inside a ${skillsSlug} session, invoke a spec directly: ${invocation}`);
79
+ }
80
+ else {
81
+ // Honest fallback: do NOT print a `zenorm work --agent <slug>` line that
82
+ // `work` would reject. `${skillsSlug}` has skills but no daemon runner.
83
+ lines.push(` 2. \`zenorm work\` does not support ${skillsSlug} — it can only run ` +
84
+ `${SUPPORTED_AGENTS.join(" or ")}.`, ` Instead, start a ${skillsSlug} session in that repo and invoke ZeNorm`, ` in-harness: ${invocation}`, ` (To use the daemon, run \`zenorm setup ${Object.keys(WORK_AGENT_BY_SKILLS_SLUG).join("` or `zenorm setup ")}\` as well.)`);
85
+ }
86
+ return lines;
87
+ }
88
+ export async function setupCommand(argv) {
89
+ const { positionals, values } = parseArgs({
90
+ args: argv,
91
+ allowPositionals: true,
92
+ options: {
93
+ global: { type: "boolean", default: false },
94
+ project: { type: "boolean", default: false },
95
+ force: { type: "boolean", default: false },
96
+ help: { type: "boolean", short: "h", default: false },
97
+ },
98
+ strict: true,
99
+ }, "setup");
100
+ if (values.help === true) {
101
+ log.plain(SETUP_USAGE);
102
+ return;
103
+ }
104
+ const requestedAgent = positionals[0];
105
+ if (!requestedAgent) {
106
+ throw new CliError(`Missing agent.\n\n${SETUP_USAGE}`);
107
+ }
108
+ if (positionals.length > 1) {
109
+ throw new CliError(`Unexpected extra argument: ${positionals[1]}\n\n${SETUP_USAGE}`);
110
+ }
111
+ if (values.global && values.project) {
112
+ throw new CliError("Choose only one scope: --global or --project.");
113
+ }
114
+ // Validate the slug BEFORE the login round-trip, so a typo fails immediately
115
+ // instead of after a browser sign-in. Shape check first, then the real
116
+ // registry check against the skills package — a well-formed but unknown slug
117
+ // (`qwen` rather than `qwen-code`) must not survive until after auth.
118
+ const agent = normalizeAgent(requestedAgent);
119
+ if (agent === "*") {
120
+ throw new CliError(`\`zenorm setup\` takes a single agent, not "${requestedAgent}".\n` +
121
+ `Run \`zenorm install-skills '*'\` to install skills for every agent.\n\n${SETUP_USAGE}`);
122
+ }
123
+ assertKnownAgent(agent);
124
+ log.info(`Setting up ZeNorm for ${agent}...`);
125
+ log.plain("");
126
+ log.info("Step 1/2 — authentication");
127
+ const authResult = await ensureAuthenticated({ force: false });
128
+ reportAuthResult(authResult);
129
+ log.plain("");
130
+ log.info("Step 2/2 — skills");
131
+ installSkillsForAgent(agent, {
132
+ project: values.project === true,
133
+ force: values.force === true,
134
+ });
135
+ log.plain(nextStepsLines(agent).join("\n"));
136
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AA2OA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D"}
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"AA0PA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D"}
@@ -1,4 +1,4 @@
1
- import { parseArgs } from "node:util";
1
+ import { parseArgs } from "../util/parse-args.js";
2
2
  import { ZenormClient } from "../api/client.js";
3
3
  import { CliError } from "../util/errors.js";
4
4
  import { log } from "../util/logger.js";
@@ -47,7 +47,7 @@ async function listTasks(argv) {
47
47
  args: argv,
48
48
  allowPositionals: true,
49
49
  strict: true,
50
- });
50
+ }, "task list");
51
51
  const specKey = positionals[0];
52
52
  if (!specKey) {
53
53
  throw new CliError("Missing spec key.\n\nUsage: zenorm task list <spec-key>");
@@ -75,7 +75,7 @@ async function updateTask(argv) {
75
75
  status: { type: "string" },
76
76
  },
77
77
  strict: true,
78
- });
78
+ }, "task update");
79
79
  const taskId = positionals[0];
80
80
  if (!taskId) {
81
81
  throw new CliError("Missing task ID.\n\nUsage: zenorm task update <task-id> --status=<status>");
@@ -105,7 +105,7 @@ async function completeTask(argv) {
105
105
  "session-id": { type: "string" },
106
106
  },
107
107
  strict: true,
108
- });
108
+ }, "task complete");
109
109
  const taskId = positionals[0];
110
110
  if (!taskId) {
111
111
  throw new CliError("Missing task ID.\n\nUsage: zenorm task complete <task-id> --summary=\"...\"");
@@ -146,7 +146,7 @@ async function startSpec(argv) {
146
146
  args: argv,
147
147
  allowPositionals: true,
148
148
  strict: true,
149
- });
149
+ }, "task start-spec");
150
150
  const specKey = positionals[0];
151
151
  if (!specKey) {
152
152
  throw new CliError("Missing spec key.\n\nUsage: zenorm task start-spec <spec-key>");
@@ -166,7 +166,7 @@ async function createTask(argv) {
166
166
  description: { type: "string" },
167
167
  },
168
168
  strict: true,
169
- });
169
+ }, "task create");
170
170
  const specKey = positionals[0];
171
171
  if (!specKey) {
172
172
  throw new CliError("Missing spec key.\n\nUsage: zenorm task create <spec-key> --title=\"...\"");
@@ -7,8 +7,8 @@ import { type TtyProbe } from "./interactive-agent.js";
7
7
  * unknown/missing `--agent` fails loud rather than silently defaulting (project
8
8
  * rule: no error-masking defaults).
9
9
  */
10
- declare const SUPPORTED_AGENTS: readonly ["codex", "claude"];
11
- type SupportedAgent = (typeof SUPPORTED_AGENTS)[number];
10
+ export declare const SUPPORTED_AGENTS: readonly ["codex", "claude"];
11
+ export type SupportedAgent = (typeof SUPPORTED_AGENTS)[number];
12
12
  /**
13
13
  * A pluggable per-task runner. The work loop calls `run(task)` after a
14
14
  * successful claim. The default is the real `AgentRunner` (which spawns the
@@ -1 +1 @@
1
- {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { parseArgs } from "node:util";
2
+ import { parseArgsErrorMessage } from "../util/parse-args.js";
2
3
  import { execFileSync, spawn } from "node:child_process";
3
4
  import { ZenormClient } from "../api/client.js";
4
5
  import { CliError } from "../util/errors.js";
@@ -11,7 +12,7 @@ import { LineSplitter, claudeRenderer, codexRenderer, daemonBanner, failureBanne
11
12
  * unknown/missing `--agent` fails loud rather than silently defaulting (project
12
13
  * rule: no error-masking defaults).
13
14
  */
14
- const SUPPORTED_AGENTS = ["codex", "claude"];
15
+ export const SUPPORTED_AGENTS = ["codex", "claude"];
15
16
  /**
16
17
  * The AC requires ready tasks to be picked up within 10 seconds, so the poll
17
18
  * interval is hard-capped at this value. Requests above the cap fail loud — we
@@ -623,8 +624,7 @@ function parseWorkArgs(argv) {
623
624
  }));
624
625
  }
625
626
  catch (err) {
626
- const message = err instanceof Error ? err.message : String(err);
627
- throw new CliError(`Invalid zenorm work arguments: ${message}\n\n${USAGE}`);
627
+ throw new CliError(`Invalid zenorm work arguments: ${parseArgsErrorMessage(err)}\n\n${USAGE}`);
628
628
  }
629
629
  const agent = values["agent"];
630
630
  if (!agent) {
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { taskCommand } from "./commands/task.js";
9
9
  import { workCommand } from "./commands/work.js";
10
10
  import { initCommand } from "./commands/init.js";
11
11
  import { installSkillsCommand } from "./commands/install-skills.js";
12
+ import { setupCommand } from "./commands/setup.js";
12
13
  import { sessionCheckCommand } from "./commands/session-check.js";
13
14
  import { getCliVersion } from "./util/package.js";
14
15
  import { armSelfUpdate } from "./util/self-update.js";
@@ -18,6 +19,7 @@ Usage:
18
19
  zenorm <command> [options]
19
20
 
20
21
  Commands:
22
+ setup <agent> One-shot onboarding: log in + install skills for an agent
21
23
  login Authenticate with ZeNorm (device auth flow)
22
24
  logout Clear stored credentials
23
25
  whoami Show current auth context
@@ -38,6 +40,7 @@ Verbosity can also be set with the LOG_LEVEL env var
38
40
  `;
39
41
  const VERBOSE_FLAGS = new Set(["--verbose", "--debug"]);
40
42
  const commands = {
43
+ setup: setupCommand,
41
44
  login: loginCommand,
42
45
  logout: logoutCommand,
43
46
  whoami: whoamiCommand,
@@ -48,6 +51,13 @@ const commands = {
48
51
  "install-skills": installSkillsCommand,
49
52
  "session-check": sessionCheckCommand,
50
53
  };
54
+ /**
55
+ * Commands that render their own `--help`. For these, the top-level handler
56
+ * forwards `--help` to the command rather than printing the global help.
57
+ * Anything not listed keeps the existing behaviour (global help), so this is
58
+ * additive and cannot change another command's output.
59
+ */
60
+ const SELF_DOCUMENTING_COMMANDS = new Set(["setup"]);
51
61
  async function main() {
52
62
  const rawArgs = process.argv.slice(2);
53
63
  // Validate LOG_LEVEL (throws CliError on bad value, caught below).
@@ -72,7 +82,11 @@ async function main() {
72
82
  log.plain(`zenorm ${getCliVersion()}`);
73
83
  return;
74
84
  }
75
- if (!commandName || args.includes("--help") || args.includes("-h")) {
85
+ const wantsHelp = args.includes("--help") || args.includes("-h");
86
+ // Top-level help: no command named, or a command that has no help of its own.
87
+ // When a command IS named and knows how to print its own usage, fall through
88
+ // so `zenorm <command> --help` shows that command's usage instead of this.
89
+ if (!commandName || (wantsHelp && !SELF_DOCUMENTING_COMMANDS.has(commandName))) {
76
90
  log.plain(helpText);
77
91
  return;
78
92
  }
@@ -83,9 +97,14 @@ async function main() {
83
97
  // Pass everything after the command name to the handler.
84
98
  // Routed to stderr (debugErr): `session-check` reserves stdout for the
85
99
  // Claude Code hook protocol, and this dispatch line runs for every command.
100
+ // Everything except the command token itself, so flags written BEFORE the
101
+ // command (`zenorm --help setup`) reach the handler just like trailing ones.
102
+ // Only the first occurrence is dropped — a later identical token is a real
103
+ // argument (e.g. `zenorm work work`), not the command name.
86
104
  const commandIndex = args.indexOf(commandName);
87
- log.debugErr(`Dispatching command "${commandName}"`, { args: args.slice(commandIndex + 1) });
88
- await handler(args.slice(commandIndex + 1));
105
+ const commandArgs = [...args.slice(0, commandIndex), ...args.slice(commandIndex + 1)];
106
+ log.debugErr(`Dispatching command "${commandName}"`, { args: commandArgs });
107
+ await handler(commandArgs);
89
108
  }
90
109
  main().catch((err) => {
91
110
  if (err instanceof CliError) {
@@ -0,0 +1,27 @@
1
+ import { parseArgs as nodeParseArgs } from "node:util";
2
+ type NodeParseArgsConfig = NonNullable<Parameters<typeof nodeParseArgs>[0]>;
3
+ /**
4
+ * Every command parses its flags with `strict: true`, so a typo like
5
+ * `zenorm login --skip-skills` makes Node throw an `ERR_PARSE_ARGS_*`
6
+ * TypeError. Those are not `CliError`s, so `index.ts` treats them as
7
+ * unexpected and dumps a raw stack trace at the user.
8
+ *
9
+ * This wrapper is the single place that translates them into a `CliError`
10
+ * with a one-line message naming the offending flag and pointing at `--help`.
11
+ * Exit code stays 1, same as any other CliError.
12
+ */
13
+ export declare function parseArgs<T extends NodeParseArgsConfig>(config: T,
14
+ /**
15
+ * The command name used in the `--help` hint, e.g. `"login"` or
16
+ * `"task update"`. Required — a generic hint would send users to the wrong
17
+ * usage text.
18
+ */
19
+ commandName: string): ReturnType<typeof nodeParseArgs<T>>;
20
+ /**
21
+ * Node's messages carry a trailing hint about passing `--` for positionals
22
+ * that start with a dash, which is noise for a mistyped flag. Keep only the
23
+ * first sentence, which is the part that names the bad option.
24
+ */
25
+ export declare function parseArgsErrorMessage(err: unknown): string;
26
+ export {};
27
+ //# sourceMappingURL=parse-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-args.d.ts","sourceRoot":"","sources":["../../src/util/parse-args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,WAAW,CAAC;AAGvD,KAAK,mBAAmB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5E;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,mBAAmB,EACrD,MAAM,EAAE,CAAC;AACT;;;;GAIG;AACH,WAAW,EAAE,MAAM,GAClB,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAMrC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQ1D"}
@@ -0,0 +1,42 @@
1
+ import { parseArgs as nodeParseArgs } from "node:util";
2
+ import { CliError } from "./errors.js";
3
+ /**
4
+ * Every command parses its flags with `strict: true`, so a typo like
5
+ * `zenorm login --skip-skills` makes Node throw an `ERR_PARSE_ARGS_*`
6
+ * TypeError. Those are not `CliError`s, so `index.ts` treats them as
7
+ * unexpected and dumps a raw stack trace at the user.
8
+ *
9
+ * This wrapper is the single place that translates them into a `CliError`
10
+ * with a one-line message naming the offending flag and pointing at `--help`.
11
+ * Exit code stays 1, same as any other CliError.
12
+ */
13
+ export function parseArgs(config,
14
+ /**
15
+ * The command name used in the `--help` hint, e.g. `"login"` or
16
+ * `"task update"`. Required — a generic hint would send users to the wrong
17
+ * usage text.
18
+ */
19
+ commandName) {
20
+ try {
21
+ return nodeParseArgs(config);
22
+ }
23
+ catch (err) {
24
+ throw new CliError(`${parseArgsErrorMessage(err)}\n\nRun \`zenorm ${commandName} --help\` for usage.`);
25
+ }
26
+ }
27
+ /**
28
+ * Node's messages carry a trailing hint about passing `--` for positionals
29
+ * that start with a dash, which is noise for a mistyped flag. Keep only the
30
+ * first sentence, which is the part that names the bad option.
31
+ */
32
+ export function parseArgsErrorMessage(err) {
33
+ if (!(err instanceof Error))
34
+ throw err;
35
+ const code = err.code;
36
+ if (typeof code !== "string" || !code.startsWith("ERR_PARSE_ARGS_"))
37
+ throw err;
38
+ const firstSentence = err.message.split(". ")[0];
39
+ if (firstSentence === undefined || firstSentence.length === 0)
40
+ throw err;
41
+ return firstSentence.endsWith(".") ? firstSentence : `${firstSentence}.`;
42
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ze-norm/cli",
3
3
  "private": false,
4
- "version": "0.14.2",
4
+ "version": "0.15.0",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "type": "module",
7
7
  "repository": {