@zitadel/cli 0.1.0 → 0.3.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 +2219 -840
- package/dist/index.mjs +9497 -9715
- package/dist/index.mjs.map +1 -1
- package/dist/lib/oclif/help.mjs +66 -20
- package/dist/lib/oclif/help.mjs.map +1 -1
- package/dist/skills-hint-N1H5-ny1.mjs +4444 -0
- package/dist/skills-hint-N1H5-ny1.mjs.map +1 -0
- package/oclif.manifest.json +4221 -1311
- package/package.json +50 -18
- package/skills/zitadel-cli/SKILL.md +198 -58
- package/skills/zitadel-cli/references/driving-login-ui.md +50 -15
- package/dist/skills-hint-BSspFzhr.mjs +0 -173
- package/dist/skills-hint-BSspFzhr.mjs.map +0 -1
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import consola$1 from "consola";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { homedir } from "node:os";
|
|
6
|
-
import pc from "picocolors";
|
|
7
|
-
import wrapAnsi from "wrap-ansi";
|
|
8
|
-
import * as rosie from "rosie-skills";
|
|
9
|
-
//#region src/lib/telemetry/identity.ts
|
|
10
|
-
/**
|
|
11
|
-
* Resolve the directory the CLI persists cross-invocation state in, honoring
|
|
12
|
-
* the platform conventions: `XDG_CONFIG_HOME` then `~/.config` on Unix, and
|
|
13
|
-
* `%APPDATA%` on Windows. The anonymous id lives here so it survives between
|
|
14
|
-
* runs without touching the user's project tree.
|
|
15
|
-
*/
|
|
16
|
-
function telemetryConfigDir(env) {
|
|
17
|
-
if (process.platform === "win32" && env.APPDATA) return join(env.APPDATA, "zitadel", "cli");
|
|
18
|
-
const base = env.XDG_CONFIG_HOME?.trim() || join(homedir(), ".config");
|
|
19
|
-
return join(base, "zitadel", "cli");
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Load the persisted anonymous id, minting and storing a new one on first run.
|
|
23
|
-
*
|
|
24
|
-
* Fail-open: if the config dir cannot be resolved, read, or written — including
|
|
25
|
-
* an `os.homedir()` throw in a restricted/containerized environment, a
|
|
26
|
-
* read-only home, a CI sandbox, or a permissions error — fall back to an
|
|
27
|
-
* ephemeral per-process id and report `isFirstRun: false` so we neither crash
|
|
28
|
-
* nor nag the user with the notice on every run. Telemetry is best-effort,
|
|
29
|
-
* never load-bearing.
|
|
30
|
-
*/
|
|
31
|
-
function loadOrCreateIdentity(env) {
|
|
32
|
-
let dir;
|
|
33
|
-
try {
|
|
34
|
-
dir = telemetryConfigDir(env);
|
|
35
|
-
} catch {
|
|
36
|
-
return {
|
|
37
|
-
distinctId: randomUUID(),
|
|
38
|
-
isFirstRun: false
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
const file = join(dir, "telemetry.json");
|
|
42
|
-
try {
|
|
43
|
-
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
44
|
-
if (typeof parsed.distinctId === "string" && parsed.distinctId.length > 0) return {
|
|
45
|
-
distinctId: parsed.distinctId,
|
|
46
|
-
isFirstRun: false
|
|
47
|
-
};
|
|
48
|
-
} catch {}
|
|
49
|
-
const distinctId = randomUUID();
|
|
50
|
-
try {
|
|
51
|
-
mkdirSync(dir, { recursive: true });
|
|
52
|
-
writeFileSync(file, `${JSON.stringify({ distinctId })}\n`, { mode: 384 });
|
|
53
|
-
return {
|
|
54
|
-
distinctId,
|
|
55
|
-
isFirstRun: true
|
|
56
|
-
};
|
|
57
|
-
} catch {
|
|
58
|
-
return {
|
|
59
|
-
distinctId,
|
|
60
|
-
isFirstRun: false
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
//#endregion
|
|
65
|
-
//#region src/lib/oclif/groups.ts
|
|
66
|
-
/**
|
|
67
|
-
* The groups the root help lists commands under, declared in print order: the
|
|
68
|
-
* journey runs from setting up a project, to running the local server, to
|
|
69
|
-
* managing its configuration, to acting on the resources inside it. Every command declares its own group
|
|
70
|
-
* (`static group` on the class) so nothing has to be registered centrally; a
|
|
71
|
-
* command without one lands in the trailing {@link ADDITIONAL_COMMANDS_GROUP}
|
|
72
|
-
* with oclif's own utilities.
|
|
73
|
-
*/
|
|
74
|
-
const CommandGroups = {
|
|
75
|
-
project: "Project commands",
|
|
76
|
-
localServer: "Local server commands",
|
|
77
|
-
configuration: "Configuration commands",
|
|
78
|
-
resources: "Resource commands"
|
|
79
|
-
};
|
|
80
|
-
const COMMAND_GROUPS = Object.values(CommandGroups);
|
|
81
|
-
/**
|
|
82
|
-
* The order within a group, which the root help and the docs' CLI reference
|
|
83
|
-
* (`./reference`) share: `groupOrder` first, lowest first, then the command id.
|
|
84
|
-
*/
|
|
85
|
-
function compareInGroup(a, b) {
|
|
86
|
-
return (a.groupOrder ?? Number.MAX_SAFE_INTEGER) - (b.groupOrder ?? Number.MAX_SAFE_INTEGER) || a.id.localeCompare(b.id);
|
|
87
|
-
}
|
|
88
|
-
/** Where ungrouped commands (oclif's `autocomplete`, `search`, …) are listed. */
|
|
89
|
-
const ADDITIONAL_COMMANDS_GROUP = "Additional commands";
|
|
90
|
-
//#endregion
|
|
91
|
-
//#region src/lib/box.ts
|
|
92
|
-
/**
|
|
93
|
-
* Frame overhead of a `consola.box` with `padding: 1`: two border columns,
|
|
94
|
-
* the padding columns, and consola's left margin. Content wrapped to the
|
|
95
|
-
* terminal width minus this stays inside the frame instead of soft-wrapping
|
|
96
|
-
* under the right border.
|
|
97
|
-
*/
|
|
98
|
-
const BOX_OVERHEAD = 8;
|
|
99
|
-
const MIN_CONTENT_WIDTH = 20;
|
|
100
|
-
const MAX_TERMINAL_WIDTH = 80;
|
|
101
|
-
/** Bold cyan, for commands the user should run (URLs/paths stay plain cyan). */
|
|
102
|
-
const command = (s) => pc.bold(pc.cyan(s));
|
|
103
|
-
/**
|
|
104
|
-
* Wraps box content to the terminal (capped at 80 columns) so consola's
|
|
105
|
-
* frame, which sizes itself to the longest content line, never exceeds the
|
|
106
|
-
* window. ANSI-aware, preserves hard line breaks and indentation, and never
|
|
107
|
-
* splits a token: URLs and commands stay whole for clicking, copying, and the
|
|
108
|
-
* journey e2e scrape (claim.spec.ts), even when such a token alone is wider
|
|
109
|
-
* than a very narrow window.
|
|
110
|
-
*/
|
|
111
|
-
function wrapForBox(message, columns = process.stdout.columns) {
|
|
112
|
-
const width = Math.max(MIN_CONTENT_WIDTH, Math.min(columns ?? MAX_TERMINAL_WIDTH, MAX_TERMINAL_WIDTH) - BOX_OVERHEAD);
|
|
113
|
-
return wrapAnsi(message, width, {
|
|
114
|
-
hard: false,
|
|
115
|
-
trim: false
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
function renderBoxActions(actions) {
|
|
119
|
-
return actions.map((action) => action.command ? `${action.text}\n\n ${command(action.command)}` : action.text).join("\n\n");
|
|
120
|
-
}
|
|
121
|
-
//#endregion
|
|
122
|
-
//#region src/lib/skills-hint.ts
|
|
123
|
-
const SHOWN_FILE = "skills-hint.json";
|
|
124
|
-
const INSTALL_COMMAND = "npx skills add zitadel/nextgen --full-depth";
|
|
125
|
-
/**
|
|
126
|
-
* Show, once and before the command's own output, a banner telling a developer
|
|
127
|
-
* who has AI coding agents installed how to give them the Zitadel skills. The
|
|
128
|
-
* CLI never installs anything itself: it names the command and the developer
|
|
129
|
-
* runs it. Modelled on Wrangler's skills suggestion, minus the prompt.
|
|
130
|
-
*
|
|
131
|
-
* Silent in CI, in non-interactive runs, when no agent is detected, once it has
|
|
132
|
-
* been shown, and when `NO_SKILLS_PROMPT` is set. Fail-open: nothing here may
|
|
133
|
-
* fail the command the user actually ran, and losing the race to create the marker
|
|
134
|
-
* (`EEXIST`) just means another run is showing the banner.
|
|
135
|
-
*/
|
|
136
|
-
async function showSkillsHint({ env, interactive }) {
|
|
137
|
-
try {
|
|
138
|
-
if (!interactive || isCI(env) || isOptedOut(env)) return;
|
|
139
|
-
const shownPath = join(telemetryConfigDir(env), SHOWN_FILE);
|
|
140
|
-
if (existsSync(shownPath)) return;
|
|
141
|
-
const names = (await rosie.agents()).filter((agent) => agent.detected && agent.installPath !== null).map((agent) => agent.display);
|
|
142
|
-
if (names.length === 0) return;
|
|
143
|
-
mkdirSync(dirname(shownPath), { recursive: true });
|
|
144
|
-
writeFileSync(shownPath, `${JSON.stringify({ date: (/* @__PURE__ */ new Date()).toISOString() })}\n`, { flag: "wx" });
|
|
145
|
-
consola$1.box({
|
|
146
|
-
title: "Zitadel skills",
|
|
147
|
-
message: wrapForBox([
|
|
148
|
-
`Zitadel detected AI coding agents on this machine: ${names.join(", ")}.`,
|
|
149
|
-
"They work best with the Zitadel skills installed. To add them, run:",
|
|
150
|
-
"",
|
|
151
|
-
` ${command(INSTALL_COMMAND)}`,
|
|
152
|
-
"",
|
|
153
|
-
"This banner shows once. Set NO_SKILLS_PROMPT=1 to never see it."
|
|
154
|
-
].join("\n")),
|
|
155
|
-
style: {
|
|
156
|
-
padding: 1,
|
|
157
|
-
borderStyle: "rounded",
|
|
158
|
-
borderColor: "cyan"
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
} catch {}
|
|
162
|
-
}
|
|
163
|
-
function isCI(env) {
|
|
164
|
-
return Boolean(env.CI) && env.CI !== "0" && env.CI !== "false";
|
|
165
|
-
}
|
|
166
|
-
function isOptedOut(env) {
|
|
167
|
-
const value = env.NO_SKILLS_PROMPT?.trim().toLowerCase();
|
|
168
|
-
return value !== void 0 && value !== "" && value !== "0" && value !== "false";
|
|
169
|
-
}
|
|
170
|
-
//#endregion
|
|
171
|
-
export { COMMAND_GROUPS as a, loadOrCreateIdentity as c, ADDITIONAL_COMMANDS_GROUP as i, renderBoxActions as n, CommandGroups as o, wrapForBox as r, compareInGroup as s, showSkillsHint as t };
|
|
172
|
-
|
|
173
|
-
//# sourceMappingURL=skills-hint-BSspFzhr.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skills-hint-BSspFzhr.mjs","names":[],"sources":["../src/lib/telemetry/identity.ts","../src/lib/oclif/groups.ts","../src/lib/box.ts","../src/lib/skills-hint.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * A stable, anonymous identifier for this install. It is a random UUID tied to\n * no account, email, or machine fingerprint — its only job is to let Mixpanel\n * group events from the same install into a funnel. `isFirstRun` is true the\n * one time we mint and persist the id, which drives the one-time consent notice.\n */\nexport type Identity = {\n readonly distinctId: string;\n readonly isFirstRun: boolean;\n};\n\ntype StoredIdentity = { distinctId: string };\n\n/**\n * Resolve the directory the CLI persists cross-invocation state in, honoring\n * the platform conventions: `XDG_CONFIG_HOME` then `~/.config` on Unix, and\n * `%APPDATA%` on Windows. The anonymous id lives here so it survives between\n * runs without touching the user's project tree.\n */\nexport function telemetryConfigDir(env: NodeJS.ProcessEnv): string {\n if (process.platform === \"win32\" && env.APPDATA) {\n return join(env.APPDATA, \"zitadel\", \"cli\");\n }\n const base = env.XDG_CONFIG_HOME?.trim() || join(homedir(), \".config\");\n return join(base, \"zitadel\", \"cli\");\n}\n\n/**\n * Load the persisted anonymous id, minting and storing a new one on first run.\n *\n * Fail-open: if the config dir cannot be resolved, read, or written — including\n * an `os.homedir()` throw in a restricted/containerized environment, a\n * read-only home, a CI sandbox, or a permissions error — fall back to an\n * ephemeral per-process id and report `isFirstRun: false` so we neither crash\n * nor nag the user with the notice on every run. Telemetry is best-effort,\n * never load-bearing.\n */\nexport function loadOrCreateIdentity(env: NodeJS.ProcessEnv): Identity {\n let dir: string;\n try {\n dir = telemetryConfigDir(env);\n } catch {\n return { distinctId: randomUUID(), isFirstRun: false };\n }\n const file = join(dir, \"telemetry.json\");\n\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as StoredIdentity;\n if (typeof parsed.distinctId === \"string\" && parsed.distinctId.length > 0) {\n return { distinctId: parsed.distinctId, isFirstRun: false };\n }\n } catch {\n /* fall through to mint a fresh id */\n }\n\n const distinctId = randomUUID();\n try {\n mkdirSync(dir, { recursive: true });\n writeFileSync(file, `${JSON.stringify({ distinctId } satisfies StoredIdentity)}\\n`, {\n mode: 0o600,\n });\n return { distinctId, isFirstRun: true };\n } catch {\n return { distinctId, isFirstRun: false };\n }\n}\n","/**\n * The groups the root help lists commands under, declared in print order: the\n * journey runs from setting up a project, to running the local server, to\n * managing its configuration, to acting on the resources inside it. Every command declares its own group\n * (`static group` on the class) so nothing has to be registered centrally; a\n * command without one lands in the trailing {@link ADDITIONAL_COMMANDS_GROUP}\n * with oclif's own utilities.\n */\nexport const CommandGroups = {\n project: \"Project commands\",\n localServer: \"Local server commands\",\n configuration: \"Configuration commands\",\n resources: \"Resource commands\",\n} as const;\n\nexport type CommandGroup = (typeof CommandGroups)[keyof typeof CommandGroups];\n\nexport const COMMAND_GROUPS: readonly CommandGroup[] = Object.values(CommandGroups);\n\n/**\n * The order within a group, which the root help and the docs' CLI reference\n * (`./reference`) share: `groupOrder` first, lowest first, then the command id.\n */\nexport function compareInGroup(\n a: { id: string; groupOrder?: number },\n b: { id: string; groupOrder?: number },\n): number {\n return (\n (a.groupOrder ?? Number.MAX_SAFE_INTEGER) - (b.groupOrder ?? Number.MAX_SAFE_INTEGER) ||\n a.id.localeCompare(b.id)\n );\n}\n\n/** Where ungrouped commands (oclif's `autocomplete`, `search`, …) are listed. */\nexport const ADDITIONAL_COMMANDS_GROUP = \"Additional commands\";\n","import pc from \"picocolors\";\nimport wrapAnsi from \"wrap-ansi\";\n\n/**\n * Frame overhead of a `consola.box` with `padding: 1`: two border columns,\n * the padding columns, and consola's left margin. Content wrapped to the\n * terminal width minus this stays inside the frame instead of soft-wrapping\n * under the right border.\n */\nconst BOX_OVERHEAD = 8;\nconst MIN_CONTENT_WIDTH = 20;\nconst MAX_TERMINAL_WIDTH = 80;\n\n/** Bold cyan, for commands the user should run (URLs/paths stay plain cyan). */\nexport const command = (s: string): string => pc.bold(pc.cyan(s));\n\n/**\n * Wraps box content to the terminal (capped at 80 columns) so consola's\n * frame, which sizes itself to the longest content line, never exceeds the\n * window. ANSI-aware, preserves hard line breaks and indentation, and never\n * splits a token: URLs and commands stay whole for clicking, copying, and the\n * journey e2e scrape (claim.spec.ts), even when such a token alone is wider\n * than a very narrow window.\n */\nexport function wrapForBox(message: string, columns = process.stdout.columns): string {\n const width = Math.max(\n MIN_CONTENT_WIDTH,\n Math.min(columns ?? MAX_TERMINAL_WIDTH, MAX_TERMINAL_WIDTH) - BOX_OVERHEAD,\n );\n return wrapAnsi(message, width, { hard: false, trim: false });\n}\n\n/**\n * A next-step line for the human box: prose plus, when the step is literally\n * a command to run, that command pulled out of the sentence. The JSON\n * envelope keeps its own plain `next_actions`/`next_commands` strings; this\n * shape exists only so {@link renderBoxActions} can put the command alone on\n * an indented styled line, where it stands out and a drag-select copies it\n * without surrounding prose. (Line selection such as triple-click still\n * includes the box border and padding — commands that must copy as a whole\n * physical line belong outside a frame, like the claim URL.)\n */\nexport type BoxAction = { text: string; command?: string };\n\nexport function renderBoxActions(actions: BoxAction[]): string {\n return actions\n .map((action) =>\n action.command ? `${action.text}\\n\\n ${command(action.command)}` : action.text,\n )\n .join(\"\\n\\n\");\n}\n","import { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nimport consola from \"consola\";\nimport * as rosie from \"rosie-skills\";\n\nimport { command, wrapForBox } from \"./box\";\nimport { telemetryConfigDir } from \"./telemetry/identity\";\n\nexport type SkillsHintInput = {\n env: NodeJS.ProcessEnv;\n /** False for `--json`, `--non-interactive`, or a non-TTY stream. */\n interactive: boolean;\n};\n\nconst SHOWN_FILE = \"skills-hint.json\";\nconst INSTALL_COMMAND = \"npx skills add zitadel/nextgen --full-depth\";\n\n/**\n * Show, once and before the command's own output, a banner telling a developer\n * who has AI coding agents installed how to give them the Zitadel skills. The\n * CLI never installs anything itself: it names the command and the developer\n * runs it. Modelled on Wrangler's skills suggestion, minus the prompt.\n *\n * Silent in CI, in non-interactive runs, when no agent is detected, once it has\n * been shown, and when `NO_SKILLS_PROMPT` is set. Fail-open: nothing here may\n * fail the command the user actually ran, and losing the race to create the marker\n * (`EEXIST`) just means another run is showing the banner.\n */\nexport async function showSkillsHint({ env, interactive }: SkillsHintInput): Promise<void> {\n try {\n if (!interactive || isCI(env) || isOptedOut(env)) {\n return;\n }\n const shownPath = join(telemetryConfigDir(env), SHOWN_FILE);\n if (existsSync(shownPath)) {\n return;\n }\n const names = (await rosie.agents())\n .filter((agent) => agent.detected && agent.installPath !== null)\n .map((agent) => agent.display);\n if (names.length === 0) {\n return;\n }\n // Created exclusively and before printing: of two concurrent first runs only\n // one gets past `wx`, and an interrupted run never shows the banner twice.\n mkdirSync(dirname(shownPath), { recursive: true });\n writeFileSync(shownPath, `${JSON.stringify({ date: new Date().toISOString() })}\\n`, {\n flag: \"wx\",\n });\n\n consola.box({\n title: \"Zitadel skills\",\n message: wrapForBox(\n [\n `Zitadel detected AI coding agents on this machine: ${names.join(\", \")}.`,\n \"They work best with the Zitadel skills installed. To add them, run:\",\n \"\",\n ` ${command(INSTALL_COMMAND)}`,\n \"\",\n \"This banner shows once. Set NO_SKILLS_PROMPT=1 to never see it.\",\n ].join(\"\\n\"),\n ),\n style: { padding: 1, borderStyle: \"rounded\", borderColor: \"cyan\" },\n });\n } catch {\n // A hint must never break `setup` or the root help.\n }\n}\n\nfunction isCI(env: NodeJS.ProcessEnv): boolean {\n return Boolean(env.CI) && env.CI !== \"0\" && env.CI !== \"false\";\n}\n\nfunction isOptedOut(env: NodeJS.ProcessEnv): boolean {\n const value = env.NO_SKILLS_PROMPT?.trim().toLowerCase();\n return value !== undefined && value !== \"\" && value !== \"0\" && value !== \"false\";\n}\n"],"mappings":"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"}
|