@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 +6 -2
- package/dist/commands/install-skills.d.ts +28 -0
- package/dist/commands/install-skills.d.ts.map +1 -1
- package/dist/commands/install-skills.js +93 -17
- package/dist/commands/login.d.ts +26 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +72 -22
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +2 -2
- package/dist/commands/setup.d.ts +18 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +136 -0
- package/dist/commands/task.d.ts.map +1 -1
- package/dist/commands/task.js +6 -6
- package/dist/commands/work.d.ts +2 -2
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +3 -3
- package/dist/index.js +22 -3
- package/dist/util/parse-args.d.ts +27 -0
- package/dist/util/parse-args.d.ts.map +1 -0
- package/dist/util/parse-args.js +42 -0
- package/package.json +1 -1
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
|
|
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":"
|
|
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 "
|
|
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
|
-
|
|
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${
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
|
186
|
+
log.debug("skills list returned unparsable JSON", {
|
|
125
187
|
error: err instanceof Error ? err.message : String(err),
|
|
126
188
|
});
|
|
127
|
-
throw new CliError(
|
|
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${
|
|
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${
|
|
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
|
-
|
|
207
|
-
|
|
280
|
+
installSkillsForAgent(requestedAgent, {
|
|
281
|
+
project: values.project === true,
|
|
282
|
+
force: values.force === true,
|
|
283
|
+
});
|
|
208
284
|
}
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
import { parseArgs } from "
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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,
|
|
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"}
|
package/dist/commands/pull.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseArgs } from "
|
|
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":"
|
|
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"}
|
package/dist/commands/task.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseArgs } from "
|
|
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=\"...\"");
|
package/dist/commands/work.d.ts
CHANGED
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/work.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
+
}
|