@pify/cli 0.1.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.
@@ -0,0 +1,195 @@
1
+ /** CLI version, read from the packaged package.json (always in the tarball). */
2
+ declare const VERSION: string;
3
+
4
+ interface CatalogPackage {
5
+ /** Short name used on the pify command line, e.g. "goal". */
6
+ name: string;
7
+ /** Full npm name, e.g. "@pify/goal". */
8
+ npm: string;
9
+ repo: string;
10
+ description: string;
11
+ /** "published" once it exists on the npm registry; "planned" before that. */
12
+ status: "published" | "planned";
13
+ }
14
+ interface Catalog {
15
+ version: number;
16
+ org: string;
17
+ scope: string;
18
+ /** URL of the always-current catalog on the org's main branch. */
19
+ remote: string;
20
+ packages: CatalogPackage[];
21
+ }
22
+ declare function loadBundledCatalog(): Catalog;
23
+ /**
24
+ * SECURITY-CRITICAL. A fetched catalog is either fully valid or fully
25
+ * discarded: every npm name must stay inside the @pify scope so a tampered
26
+ * catalog cannot redirect installs outside the org (npm publish rights are a
27
+ * separate credential from GitHub). Combined with the install rule that only
28
+ * `npm:@pify/...` specs are ever delegated to pi, this bounds the blast
29
+ * radius of a compromised remote to "packages the org itself published".
30
+ */
31
+ declare function validateCatalog(data: unknown): data is Catalog;
32
+ /**
33
+ * Load the catalog. Resolution chain, newest `version` wins:
34
+ * bundled snapshot (offline floor) -> ~/.pify cache -> remote fetch.
35
+ * Opportunistic refresh (3s timeout) when the cache is stale; forced refresh
36
+ * (10s) via `refreshCatalog`. Every failure path falls back silently — the
37
+ * catalog can never break a command.
38
+ */
39
+ declare function loadCatalog(opts?: {
40
+ refresh?: boolean;
41
+ }): Promise<Catalog>;
42
+ /** Forced refresh for `pify update --catalog`. Reports success. */
43
+ declare function refreshCatalog(): Promise<{
44
+ ok: boolean;
45
+ catalog: Catalog;
46
+ }>;
47
+ interface InstallSpec {
48
+ base: string;
49
+ pin: string | null;
50
+ }
51
+ /** Split `goal@0.3.0` / `@pify/goal@next` into base and validated pin. */
52
+ declare function parseInstallSpec(input: string): InstallSpec;
53
+ interface InstallTarget {
54
+ shortName: string;
55
+ npm: string;
56
+ pin: string | null;
57
+ /** True when the user typed the full @pify/<name> form (catalog bypass). */
58
+ explicit: boolean;
59
+ inCatalog: boolean;
60
+ status: CatalogPackage["status"] | null;
61
+ repo: string | null;
62
+ }
63
+ /**
64
+ * Resolve a user-supplied install argument. Short names must exist in the
65
+ * catalog and be published; the explicit `@pify/<name>` form always proceeds
66
+ * (the escape hatch for catalog staleness — a truly unpublished package
67
+ * degrades to pi/npm's own E404, never a wrong install). Anything outside the
68
+ * @pify scope is refused: that is pi's job.
69
+ */
70
+ declare function resolveInstallTarget(catalog: Catalog, input: string): InstallTarget;
71
+ /**
72
+ * Resolve a name for remove/update targeting: same input forms, but catalog
73
+ * membership is not required (users must be able to remove packages later
74
+ * dropped from the catalog). Pins are parsed and ignored.
75
+ */
76
+ declare function resolvePifyName(input: string): string;
77
+
78
+ /** The npm package that provides the `pi` binary. */
79
+ declare const PI_PACKAGE = "@earendil-works/pi-coding-agent";
80
+ /** Minimum Node version required by the pi coding agent. */
81
+ declare const NODE_FLOOR = "22.19.0";
82
+ /** The pi version this CLI release was developed and tested against. */
83
+ declare const TESTED_PI_VERSION = "0.84.4";
84
+ interface PiStatus {
85
+ installed: boolean;
86
+ /** Absolute path to the `pi` launcher, when installed. */
87
+ binPath: string | null;
88
+ /** Version reported by `pi --version`, when installed. */
89
+ version: string | null;
90
+ }
91
+ declare function piStatus(): PiStatus;
92
+ /** Latest pi version from pi's own release endpoint. Null offline/on error. */
93
+ declare function fetchLatestPiVersion(): Promise<string | null>;
94
+ declare function compareSemver(a: string, b: string): number;
95
+ /**
96
+ * Install pi globally via npm. This is the one operation pi cannot do for
97
+ * itself — `pi update --self` requires a working pi. `--ignore-scripts`
98
+ * matches pi's own self-update invocation (supply-chain-safe default).
99
+ */
100
+ declare function installPi(version?: string): Promise<void>;
101
+ /**
102
+ * Update pi. Delegates to bare `pi update` (its default target is self):
103
+ * pi's updater owns install-method detection, the pi.dev rename protocol,
104
+ * managed-install staging, and the Windows native-module quarantine.
105
+ * Reimplementing any of it would rot. When pi is absent, fall through to a
106
+ * fresh install so `npx @pify/cli update` works on a bare machine.
107
+ */
108
+ declare function updatePi(): Promise<void>;
109
+ interface PiSettings {
110
+ packages?: Array<string | {
111
+ source: string;
112
+ [k: string]: unknown;
113
+ }>;
114
+ [k: string]: unknown;
115
+ }
116
+ /** pi's agent directory: tilde-expanded PI_CODING_AGENT_DIR or ~/.pi/agent. */
117
+ declare function agentDir(): string;
118
+ /**
119
+ * Read-only view of pi settings. All writes go through the pi CLI — pi owns
120
+ * that state (dedup rules, npm workspace, trust model), and two writers would
121
+ * eventually disagree. Parse failures are treated as absent (doctor warns).
122
+ */
123
+ declare function readSettings(scope: "user" | "project"): PiSettings | null;
124
+ interface InstalledPifyPackage {
125
+ name: string;
126
+ scope: "user" | "project";
127
+ source: string;
128
+ pin: string | null;
129
+ }
130
+ /**
131
+ * @pify packages configured in pi's settings, both scopes, deduped by name
132
+ * (project wins, matching pi's own scope precedence).
133
+ */
134
+ declare function installedPifyPackages(): Map<string, InstalledPifyPackage>;
135
+ interface OnDiskState {
136
+ present: boolean;
137
+ version: string | null;
138
+ }
139
+ /** Version of an installed @pify package as materialized in pi's npm root. */
140
+ declare function installedVersionOnDisk(name: string, scope: "user" | "project"): OnDiskState;
141
+
142
+ /** True when the user asked for offline behavior (pi's env var is honored too). */
143
+ declare function isOffline(): boolean;
144
+ declare function assertSafeArg(arg: string, what?: string): void;
145
+ /**
146
+ * Absolute path of a command on PATH, or null. In-process scan (no child
147
+ * process): PATHEXT-aware on Windows, executable-bit check on POSIX.
148
+ */
149
+ declare function which(command: string): string | null;
150
+
151
+ /**
152
+ * The five `pify init` file templates, embedded as string literals so the
153
+ * tarball ships nothing extra. Every line of the package.json template
154
+ * encodes a verified ecosystem rule — see the comments in initCommand.
155
+ */
156
+ interface TemplateVars {
157
+ name: string;
158
+ description: string;
159
+ shortName: string;
160
+ snakeName: string;
161
+ piVersion: string;
162
+ dir: string;
163
+ }
164
+ declare function interpolate(template: string, vars: TemplateVars): string;
165
+ declare function packageJsonTemplate(scoped: boolean): string;
166
+ declare const INDEX_TS = "import { Type } from \"@earendil-works/pi-ai\";\nimport { defineTool, type ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\n\n// Rules this template already follows:\n// - Signal tool errors by THROWING from execute(); returned values never set isError.\n// - Truncate large output with truncateHead/truncateTail from\n// @earendil-works/pi-coding-agent (built-in limits: 50 KB / 2000 lines).\n// - Use StringEnum from @earendil-works/pi-ai for enum parameters;\n// Type.Union/Type.Literal breaks on Google's API.\n// - Use the CONFIG_DIR_NAME export instead of hardcoding \".pi\" for\n// project-local config paths.\nconst exampleTool = defineTool({\n name: \"{{snakeName}}_hello\",\n label: \"{{shortName}} hello\",\n description: \"Example tool for {{name}}. Replace with your own.\",\n parameters: Type.Object({\n name: Type.String({ description: \"Name to greet\" }),\n }),\n async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n return {\n content: [{ type: \"text\", text: `Hello, ${params.name}!` }],\n details: { greeted: params.name },\n };\n },\n});\n\nexport default function (pi: ExtensionAPI) {\n pi.registerTool(exampleTool);\n\n pi.registerCommand(\"{{shortName}}-hello\", {\n description: \"Example command for {{name}}. Replace with your own.\",\n handler: async (args, _ctx) => {\n // `args` is the single raw string typed after the command name.\n // There is no argv parsing - split it yourself if you need arguments.\n },\n });\n\n // Never start watchers, sockets, timers, or child processes here in the\n // factory: it can run in invocations that never start a session.\n pi.on(\"session_start\", async (_event, _ctx) => {\n // Start background resources here.\n });\n\n pi.on(\"session_shutdown\", async (_event, _ctx) => {\n // Tear them down here. This must be idempotent: it fires on quit,\n // /reload, /new, /resume, and /fork.\n });\n}\n";
167
+ declare const README_MD = "# {{name}}\n\n{{description}}\n\nA Pi Package for the pi coding agent (https://pi.dev).\n\n## Develop\n\n npm install # dev dependencies for typechecking\n pi install ./{{dir}} # register locally; hot-reloads with /reload\n pi -e ./index.ts # or load ad hoc for a single run\n npm run typecheck\n\n## Publish\n\n npm publish --access public\n\nThen install anywhere with: pi install npm:{{name}}\n\nTested with pi {{piVersion}}.\n";
168
+
169
+ /**
170
+ * Exit codes. Distinct codes let CI distinguish "your environment is wrong"
171
+ * from "the thing you asked for does not exist".
172
+ */
173
+ declare const ExitCode: {
174
+ readonly OK: 0;
175
+ /** Generic / unexpected failure. */
176
+ readonly FAILURE: 1;
177
+ /** Bad arguments, unknown command, malformed package name. */
178
+ readonly USAGE: 2;
179
+ /** A required external program (pi, npm, node) is missing or too old. */
180
+ readonly ENVIRONMENT: 3;
181
+ /** The requested package is not in the catalog or not yet published. */
182
+ readonly NOT_FOUND: 4;
183
+ /** A delegated `pi` or `npm` subprocess exited non-zero. */
184
+ readonly SUBPROCESS: 5;
185
+ };
186
+ type ExitCodeValue = (typeof ExitCode)[keyof typeof ExitCode];
187
+ /** An error we produced deliberately: message is user-facing, no stack trace. */
188
+ declare class PifyError extends Error {
189
+ readonly code: ExitCodeValue;
190
+ /** Optional follow-up shown after the message, e.g. a command to run. */
191
+ readonly hint?: string;
192
+ constructor(message: string, code?: ExitCodeValue, hint?: string);
193
+ }
194
+
195
+ export { type Catalog, type CatalogPackage, ExitCode, INDEX_TS, type InstallSpec, type InstallTarget, type InstalledPifyPackage, NODE_FLOOR, PI_PACKAGE, type PiSettings, type PiStatus, PifyError, README_MD, TESTED_PI_VERSION, type TemplateVars, VERSION, agentDir, assertSafeArg, compareSemver, fetchLatestPiVersion, installPi, installedPifyPackages, installedVersionOnDisk, interpolate, isOffline, loadBundledCatalog, loadCatalog, packageJsonTemplate, parseInstallSpec, piStatus, readSettings, refreshCatalog, resolveInstallTarget, resolvePifyName, updatePi, validateCatalog, which };