primitive-admin 1.0.49 → 1.0.50

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.
Files changed (120) hide show
  1. package/README.md +102 -2
  2. package/assets/skill/skills/primitive-platform/SKILL.md +85 -30
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +66 -1
  5. package/dist/bin/primitive.js.map +1 -1
  6. package/dist/src/commands/admins.d.ts +2 -0
  7. package/dist/src/commands/analytics.d.ts +2 -0
  8. package/dist/src/commands/apps.d.ts +2 -0
  9. package/dist/src/commands/apps.js +20 -0
  10. package/dist/src/commands/apps.js.map +1 -1
  11. package/dist/src/commands/auth.d.ts +2 -0
  12. package/dist/src/commands/blob-buckets.d.ts +2 -0
  13. package/dist/src/commands/catalog.d.ts +2 -0
  14. package/dist/src/commands/collection-type-configs.d.ts +2 -0
  15. package/dist/src/commands/collections.d.ts +2 -0
  16. package/dist/src/commands/comparisons.d.ts +2 -0
  17. package/dist/src/commands/cron-triggers.d.ts +2 -0
  18. package/dist/src/commands/cron-triggers.js +8 -15
  19. package/dist/src/commands/cron-triggers.js.map +1 -1
  20. package/dist/src/commands/database-types.d.ts +2 -0
  21. package/dist/src/commands/databases.d.ts +2 -0
  22. package/dist/src/commands/databases.js +31 -0
  23. package/dist/src/commands/databases.js.map +1 -1
  24. package/dist/src/commands/documents.d.ts +2 -0
  25. package/dist/src/commands/email-templates.d.ts +2 -0
  26. package/dist/src/commands/env.d.ts +12 -0
  27. package/dist/src/commands/group-type-configs.d.ts +2 -0
  28. package/dist/src/commands/groups.d.ts +2 -0
  29. package/dist/src/commands/guides.d.ts +84 -0
  30. package/dist/src/commands/guides.js +201 -24
  31. package/dist/src/commands/guides.js.map +1 -1
  32. package/dist/src/commands/init.d.ts +17 -0
  33. package/dist/src/commands/init.js +63 -25
  34. package/dist/src/commands/init.js.map +1 -1
  35. package/dist/src/commands/integrations.d.ts +2 -0
  36. package/dist/src/commands/integrations.js +22 -5
  37. package/dist/src/commands/integrations.js.map +1 -1
  38. package/dist/src/commands/llm.d.ts +2 -0
  39. package/dist/src/commands/prompts.d.ts +2 -0
  40. package/dist/src/commands/rule-sets.d.ts +2 -0
  41. package/dist/src/commands/secrets.d.ts +2 -0
  42. package/dist/src/commands/skill.d.ts +2 -0
  43. package/dist/src/commands/sync.d.ts +113 -0
  44. package/dist/src/commands/sync.js +366 -12
  45. package/dist/src/commands/sync.js.map +1 -1
  46. package/dist/src/commands/tokens.d.ts +2 -0
  47. package/dist/src/commands/tokens.js +104 -1
  48. package/dist/src/commands/tokens.js.map +1 -1
  49. package/dist/src/commands/users.d.ts +2 -0
  50. package/dist/src/commands/waitlist.d.ts +2 -0
  51. package/dist/src/commands/waitlist.js +1 -1
  52. package/dist/src/commands/waitlist.js.map +1 -1
  53. package/dist/src/commands/webhooks.d.ts +2 -0
  54. package/dist/src/commands/workflows.d.ts +49 -0
  55. package/dist/src/commands/workflows.js +74 -21
  56. package/dist/src/commands/workflows.js.map +1 -1
  57. package/dist/src/lib/api-client.d.ts +1244 -0
  58. package/dist/src/lib/api-client.js +30 -0
  59. package/dist/src/lib/api-client.js.map +1 -1
  60. package/dist/src/lib/auth-flow.d.ts +8 -0
  61. package/dist/src/lib/cli-manifest.d.ts +60 -0
  62. package/dist/src/lib/cli-manifest.js +70 -0
  63. package/dist/src/lib/cli-manifest.js.map +1 -0
  64. package/dist/src/lib/config.d.ts +37 -0
  65. package/dist/src/lib/confirm-prompt.d.ts +66 -0
  66. package/dist/src/lib/confirm-prompt.js +85 -0
  67. package/dist/src/lib/confirm-prompt.js.map +1 -0
  68. package/dist/src/lib/constants.d.ts +2 -0
  69. package/dist/src/lib/crash-handlers.d.ts +20 -0
  70. package/dist/src/lib/crash-handlers.js +49 -0
  71. package/dist/src/lib/crash-handlers.js.map +1 -0
  72. package/dist/src/lib/credentials-store.d.ts +79 -0
  73. package/dist/src/lib/csv.d.ts +48 -0
  74. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  75. package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
  76. package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
  77. package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
  78. package/dist/src/lib/db-codegen/dbTemplates.js +31 -10
  79. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  80. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
  81. package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
  82. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
  83. package/dist/src/lib/env-resolver.d.ts +62 -0
  84. package/dist/src/lib/fetch.d.ts +5 -0
  85. package/dist/src/lib/init-config.d.ts +46 -0
  86. package/dist/src/lib/init-config.js +7 -0
  87. package/dist/src/lib/init-config.js.map +1 -1
  88. package/dist/src/lib/migration-nag.d.ts +49 -0
  89. package/dist/src/lib/output.d.ts +49 -0
  90. package/dist/src/lib/output.js +25 -1
  91. package/dist/src/lib/output.js.map +1 -1
  92. package/dist/src/lib/paginate.d.ts +33 -0
  93. package/dist/src/lib/project-config.d.ts +97 -0
  94. package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
  95. package/dist/src/lib/resolve-platform.d.ts +45 -0
  96. package/dist/src/lib/resolve-platform.js +43 -0
  97. package/dist/src/lib/resolve-platform.js.map +1 -0
  98. package/dist/src/lib/skill-installer.d.ts +23 -0
  99. package/dist/src/lib/snapshots.d.ts +99 -0
  100. package/dist/src/lib/snapshots.js +357 -0
  101. package/dist/src/lib/snapshots.js.map +1 -0
  102. package/dist/src/lib/sync-paths.d.ts +72 -0
  103. package/dist/src/lib/sync-paths.js +29 -1
  104. package/dist/src/lib/sync-paths.js.map +1 -1
  105. package/dist/src/lib/template.d.ts +93 -0
  106. package/dist/src/lib/token-inject.d.ts +56 -0
  107. package/dist/src/lib/token-inject.js +204 -0
  108. package/dist/src/lib/token-inject.js.map +1 -0
  109. package/dist/src/lib/toml-database-config.d.ts +132 -0
  110. package/dist/src/lib/toml-params-validator.d.ts +95 -0
  111. package/dist/src/lib/version-check.d.ts +10 -0
  112. package/dist/src/lib/workflow-fragments.d.ts +41 -0
  113. package/dist/src/lib/workflow-toml-validator.d.ts +86 -0
  114. package/dist/src/lib/workflow-toml-validator.js +31 -1
  115. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  116. package/dist/src/types/index.d.ts +513 -0
  117. package/dist/src/validators.d.ts +64 -0
  118. package/dist/src/validators.js +63 -0
  119. package/dist/src/validators.js.map +1 -0
  120. package/package.json +7 -1
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Pure helpers for the `primitive tokens inject` command (issue #420).
3
+ *
4
+ * Kept separate from the commander wrapper in `commands/tokens.ts` so the
5
+ * snippet building, shared-secret generation, and one-shot localhost server
6
+ * can be unit-tested without a live backend or a spawned CLI process.
7
+ */
8
+ import { createServer } from "node:http";
9
+ import { randomBytes } from "node:crypto";
10
+ import { spawnSync } from "node:child_process";
11
+ /** Default TTL for injected tokens — short-lived per the #420 design (30 minutes). */
12
+ export const DEFAULT_INJECT_TTL = "30m";
13
+ /** Lowest port the `--serve` one-shot server is allowed to bind. */
14
+ const MIN_SERVE_PORT = 30001;
15
+ /** How many random ports to try before giving up on collisions. */
16
+ const SERVE_PORT_ATTEMPTS = 5;
17
+ /**
18
+ * Escape a token for safe embedding inside a single-quoted JS string literal.
19
+ * Tokens are `prim_<base64url>` so they normally need no escaping, but we are
20
+ * defensive in case the source ever changes.
21
+ */
22
+ function escapeForJsSingleQuote(value) {
23
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
24
+ }
25
+ /**
26
+ * Build the browser console one-liner that applies a token via the
27
+ * `window.__PRIMITIVE_SET_TOKEN__` hook installed by `<DevTools>`.
28
+ */
29
+ export function buildSnippet(token) {
30
+ return `window.__PRIMITIVE_SET_TOKEN__('${escapeForJsSingleQuote(token)}')`;
31
+ }
32
+ /** Render the output for a given `--print` mode. */
33
+ export function buildPrintOutput(mode, token) {
34
+ switch (mode) {
35
+ case "snippet":
36
+ return buildSnippet(token);
37
+ case "token":
38
+ return token;
39
+ case "env":
40
+ return `VITE_PRIMITIVE_DEV_TOKEN=${token}`;
41
+ default:
42
+ throw new Error(`Invalid --print mode: ${mode}. Expected one of: snippet, env, token.`);
43
+ }
44
+ }
45
+ /** Generate a random, URL-safe shared secret for the `--serve` endpoint. */
46
+ export function generateSharedSecret() {
47
+ return randomBytes(16).toString("hex");
48
+ }
49
+ /** A random integer in [min, max]. */
50
+ function randomPort() {
51
+ // 30001..65535
52
+ const span = 65535 - MIN_SERVE_PORT;
53
+ return MIN_SERVE_PORT + Math.floor(Math.random() * span);
54
+ }
55
+ /**
56
+ * Copy text to the system clipboard using whatever native tool is available.
57
+ * No new npm dependency — shells out to `pbcopy` (macOS), `xclip`/`xsel`
58
+ * (Linux), or `clip` (Windows). Returns `true` on success, `false` if no
59
+ * clipboard tool is available or the copy failed (caller degrades gracefully).
60
+ */
61
+ export function copyToClipboard(text) {
62
+ const candidates = process.platform === "darwin"
63
+ ? [{ cmd: "pbcopy", args: [] }]
64
+ : process.platform === "win32"
65
+ ? [{ cmd: "clip", args: [] }]
66
+ : [
67
+ { cmd: "xclip", args: ["-selection", "clipboard"] },
68
+ { cmd: "xsel", args: ["--clipboard", "--input"] },
69
+ { cmd: "wl-copy", args: [] },
70
+ ];
71
+ for (const { cmd, args } of candidates) {
72
+ try {
73
+ const res = spawnSync(cmd, args, { input: text });
74
+ if (res.error)
75
+ continue; // command not found — try next
76
+ if (res.status === 0)
77
+ return true;
78
+ }
79
+ catch {
80
+ // try next candidate
81
+ }
82
+ }
83
+ return false;
84
+ }
85
+ /**
86
+ * CORS headers for the one-shot token response. `--serve` is documented to be
87
+ * read via `fetch('http://127.0.0.1:PORT/...')` FROM the app's browser console,
88
+ * which is a cross-origin request. Without `Access-Control-Allow-Origin` the
89
+ * browser blocks `r.text()`, so the advertised browser-injection flow fails.
90
+ * We echo back the request `Origin` (falling back to `*`) and allow `GET`.
91
+ *
92
+ * Note: these headers do NOT weaken the loopback-bind + shared-secret model.
93
+ * CORS only governs whether the browser exposes the RESPONSE to page JS; the
94
+ * 127.0.0.1 bind and the unguessable per-run secret are what gate access.
95
+ */
96
+ function corsHeaders(reqOrigin) {
97
+ return {
98
+ "Access-Control-Allow-Origin": reqOrigin ?? "*",
99
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
100
+ "Access-Control-Allow-Headers": "*",
101
+ Vary: "Origin",
102
+ };
103
+ }
104
+ /**
105
+ * Boot a one-shot HTTP server bound to `127.0.0.1` that serves `token` exactly
106
+ * once, then shuts down. Security model:
107
+ * - binds loopback only (never 0.0.0.0)
108
+ * - requires a random shared-secret `?key=` query param; requests without the
109
+ * correct secret get 403 and do NOT consume the one-shot read
110
+ * - returns CORS headers so the documented cross-origin browser `fetch` can
111
+ * actually read the response; a CORS preflight (`OPTIONS`) is answered 204
112
+ * WITHOUT consuming the one-shot read or shutting the server down
113
+ * - the one-shot consumption + shutdown happen ONLY on the authorized `GET`
114
+ * that actually delivers the token (correct secret) — never on a preflight,
115
+ * a secret-rejected request, or a wrong path
116
+ * - retries random ports >30000 on collision (>= 5 attempts)
117
+ */
118
+ export async function serveTokenOnce(token, opts = {}) {
119
+ const secret = generateSharedSecret();
120
+ const path = opts.path ?? "/token";
121
+ let resolveDone;
122
+ const done = new Promise((resolve) => {
123
+ resolveDone = resolve;
124
+ });
125
+ let served = false;
126
+ const server = createServer((req, res) => {
127
+ const reqUrl = new URL(req.url ?? "/", "http://127.0.0.1");
128
+ const cors = corsHeaders(req.headers.origin);
129
+ // CORS preflight: the browser sends an OPTIONS before the cross-origin GET.
130
+ // Answer it with the CORS headers and 204, but do NOT consume the one-shot
131
+ // read or shut the server down — the real GET still needs to deliver the
132
+ // token afterward.
133
+ if (req.method === "OPTIONS") {
134
+ res.writeHead(204, cors);
135
+ res.end();
136
+ return;
137
+ }
138
+ if (reqUrl.pathname !== path) {
139
+ res.writeHead(404, { "Content-Type": "text/plain", ...cors });
140
+ res.end("Not found");
141
+ return;
142
+ }
143
+ // Reject any request missing the correct shared secret. Does not consume
144
+ // the one-shot read, so a racing process cannot lock the dev out.
145
+ if (reqUrl.searchParams.get("key") !== secret) {
146
+ res.writeHead(403, { "Content-Type": "text/plain", ...cors });
147
+ res.end("Forbidden");
148
+ return;
149
+ }
150
+ if (served) {
151
+ res.writeHead(410, { "Content-Type": "text/plain", ...cors });
152
+ res.end("Token already served");
153
+ return;
154
+ }
155
+ // Authorized GET with the correct secret: this is the only path that
156
+ // consumes the one-shot read and shuts the server down.
157
+ served = true;
158
+ res.writeHead(200, { "Content-Type": "text/plain", ...cors });
159
+ res.end(token);
160
+ // Close after the response flushes so the single read completes cleanly.
161
+ res.on("finish", () => {
162
+ server.close(() => resolveDone());
163
+ });
164
+ });
165
+ const port = await listenWithRetry(server);
166
+ const url = `http://127.0.0.1:${port}${path}?key=${secret}`;
167
+ const stop = () => {
168
+ try {
169
+ server.close();
170
+ }
171
+ catch {
172
+ // already closed
173
+ }
174
+ };
175
+ return { url, secret, port, done, stop };
176
+ }
177
+ /** Try random ports > 30000 until one binds, up to SERVE_PORT_ATTEMPTS times. */
178
+ function listenWithRetry(server) {
179
+ return new Promise((resolve, reject) => {
180
+ let attempts = 0;
181
+ const tryListen = () => {
182
+ attempts += 1;
183
+ const port = randomPort();
184
+ const onError = (err) => {
185
+ server.removeListener("listening", onListening);
186
+ if (err.code === "EADDRINUSE" && attempts < SERVE_PORT_ATTEMPTS) {
187
+ tryListen();
188
+ }
189
+ else {
190
+ reject(new Error(`Failed to bind a local port for --serve after ${attempts} attempt(s): ${err.message}`));
191
+ }
192
+ };
193
+ const onListening = () => {
194
+ server.removeListener("error", onError);
195
+ resolve(port);
196
+ };
197
+ server.once("error", onError);
198
+ server.once("listening", onListening);
199
+ server.listen(port, "127.0.0.1");
200
+ };
201
+ tryListen();
202
+ });
203
+ }
204
+ //# sourceMappingURL=token-inject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-inject.js","sourceRoot":"","sources":["../../../src/lib/token-inject.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Helpers for emitting `DatabaseTypeOperation` configs as native TOML.
3
+ *
4
+ * Background (issue #752): historically `sync pull` serialized
5
+ * `definition` and `params` as JSON strings stuffed into a single TOML
6
+ * field, which produced unreviewable 800-char one-liners. This module
7
+ * lets us emit them as nested TOML tables when they're representable,
8
+ * while preserving the existing file's shape so we don't generate
9
+ * surprise diffs.
10
+ *
11
+ * Three responsibilities:
12
+ * 1. `detectExistingOperationForms()` — read the raw TOML source and
13
+ * decide, per operation name, whether the file uses JSON-string
14
+ * form ("legacy") or nested-table form ("native") for each of
15
+ * `definition` and `params`. This drives form preservation on
16
+ * `sync pull`.
17
+ * 2. `canEmitNative()` — recursively check whether a JS value is
18
+ * representable as a TOML value. Returns a reason string if not.
19
+ * Mainly catches `null` and mixed-type arrays, which `@iarna/toml`
20
+ * either rejects or silently mangles.
21
+ * 3. `buildDatabaseTypeTomlData()` — builds the JS object that we
22
+ * hand to `TOML.stringify`. Per operation, decides JSON-string vs
23
+ * native based on (existing form, can-emit, override mode).
24
+ */
25
+ export type FieldForm = "native" | "legacy";
26
+ export interface OperationFormHints {
27
+ /** Map of op name → existing form for `definition`. Missing op = "default". */
28
+ definition: Map<string, FieldForm>;
29
+ /** Map of op name → existing form for `params`. Missing op = "default". */
30
+ params: Map<string, FieldForm>;
31
+ }
32
+ /**
33
+ * Inspect the raw TOML source for a database-type file and decide which
34
+ * form each operation's `definition`/`params` currently use.
35
+ *
36
+ * Detection is intentionally simple: we walk `[[operations]]` blocks in
37
+ * source order and, for each one, look for either a `definition = '...'`
38
+ * line (legacy) or an `[operations.definition]` / `[operations.definition.X]`
39
+ * sub-header (native).
40
+ */
41
+ export declare function detectExistingOperationForms(rawToml: string): OperationFormHints;
42
+ /**
43
+ * Recursively check whether a JS value can be safely serialized to TOML
44
+ * using `@iarna/toml`. Returns null if OK, a reason string otherwise.
45
+ *
46
+ * Things TOML can't represent (in `@iarna/toml`):
47
+ * - `null` (TOML has no null).
48
+ * - Arrays containing values of different "TOML types" (e.g. mixed
49
+ * string and number). Spec-wise TOML 1.0 actually allows this, but
50
+ * `@iarna/toml` is strict and throws.
51
+ * - `undefined` (different from null but equally not representable).
52
+ */
53
+ export declare function canEmitNative(value: any): string | null;
54
+ /**
55
+ * Normalize a `params` value into a sorted array of
56
+ * `{ name, type, required, ... }` rows, suitable for emitting as
57
+ * `[[operations.params]]`. Accepts both shapes the wire/parser supports:
58
+ * - object form: `{ projectId: { type: "string", required: true } }`
59
+ * - array form: `[{ name: "projectId", type: "string", required: true }]`
60
+ */
61
+ export declare function normalizeParamsToArray(params: any): any[] | null;
62
+ export interface BuildOptions {
63
+ /** Per-op form hints derived from `detectExistingOperationForms`. */
64
+ hints?: OperationFormHints;
65
+ /**
66
+ * Override the default form for ops with no hint. Used by `sync pull`
67
+ * to keep behavior backwards-compatible if we ever flip the default,
68
+ * and by `sync migrate-toml` to force native everywhere.
69
+ */
70
+ defaultForm?: FieldForm;
71
+ /** Sink for human-readable fallback messages. */
72
+ logger?: (message: string) => void;
73
+ /**
74
+ * Server `DatabaseTypeSubscription` rows to emit as `[[subscriptions]]`
75
+ * blocks (issue #803). Already sanitized by the controller's
76
+ * `sanitizeSubscription` (so `select`/`emit` are arrays and `params` is an
77
+ * object). Omitted on the `migrate-toml` path (no subscriptions to merge).
78
+ */
79
+ subscriptions?: any[];
80
+ }
81
+ /**
82
+ * Build the JS object to pass to `TOML.stringify` for a database-type
83
+ * config file. Per operation:
84
+ * - If the chosen form is "native" AND `definition` is TOML-emittable,
85
+ * emit `[operations.definition]` (nested table).
86
+ * - Else emit `definition = '<json string>'` and log a message.
87
+ * - Same logic independently for `params`, but the native form for
88
+ * params is `[[operations.params]]` (array-of-tables).
89
+ */
90
+ export declare function buildDatabaseTypeTomlData(typeConfig: any, operations: any[], ruleSetIdToName: Map<string, string>, options?: BuildOptions): any;
91
+ /**
92
+ * Thrown when a `[[subscriptions]]` block declares both `accessRule` and
93
+ * `access` with different values. We never silently drop the field
94
+ * (issue #803 maintainer decision): prefer `access` when they agree, error
95
+ * when they conflict so the operator fixes the source of truth.
96
+ */
97
+ export declare class SubscriptionAccessKeyConflictError extends Error {
98
+ subscriptionKey: string | undefined;
99
+ constructor(subscriptionKey: string | undefined);
100
+ }
101
+ /**
102
+ * Resolve the subscription `access` CEL expression from a TOML row.
103
+ *
104
+ * The server's wire key is `access`; the issue author (and the guides) used
105
+ * `accessRule`. We accept both: prefer `access` when present, fall back to
106
+ * `accessRule`, and never silently drop the field. If both are present with
107
+ * different values, throw so the conflict is surfaced rather than guessed.
108
+ */
109
+ export declare function resolveSubscriptionAccess(sub: any): string | undefined;
110
+ /**
111
+ * Normalize a `[[subscriptions]]` TOML row into the server's wire shape.
112
+ *
113
+ * Mirrors `normalizeOperationFromToml`. Maps the `accessRule`→`access`
114
+ * alias (issue #803), and passes `select` / `emit` / `params` through in
115
+ * native form — the server stores them as JSON strings and re-parses on
116
+ * read (`sanitizeSubscription`), so the parse → push → pull round-trip is
117
+ * symmetric with operations' `definition` / `params`.
118
+ *
119
+ * `filter` stays required server-side; we forward whatever is present (or
120
+ * undefined) and let the controller's clean named 400 surface.
121
+ */
122
+ export declare function normalizeSubscriptionFromToml(sub: any): any;
123
+ /**
124
+ * After parsing TOML, normalize the `definition` and `params` of each
125
+ * operation row into the JS objects the server expects. Handles both
126
+ * native and legacy forms transparently. The server payload is the
127
+ * source of truth; native TOML is purely a source-control affordance.
128
+ *
129
+ * Mirrors `parseDatabaseTypeToml` in `sync.ts`, but is broken out here
130
+ * so the migrate-toml command can call it without re-implementing.
131
+ */
132
+ export declare function normalizeOperationFromToml(op: any): any;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Validator: every `$params.X` reference inside an operation's
3
+ * `definition` must correspond to a declared param in
4
+ * `[[operations.params]]` (or the legacy JSON-string params object).
5
+ *
6
+ * Background (issue #752): a typo like `$params.proectId` used to
7
+ * silently no-op at runtime. With native TOML, we can catch these at
8
+ * `sync push` time with the file path and line number of the
9
+ * operation block where the bad reference appears.
10
+ *
11
+ * Design notes:
12
+ * - Line attribution is per-operation, not per-reference. We don't
13
+ * parse `definition` line-by-line; we just locate the
14
+ * `[[operations]]` header for the named op in the raw source and
15
+ * report that line. This is the load-bearing UX (jump to the
16
+ * offending op block); pinpointing the exact `$params.X` reference
17
+ * inside a sub-table would require swapping the TOML parser, which
18
+ * the issue explicitly leaves to implementer judgement.
19
+ * - We intentionally diverge from the server-side `collectParamRefs`
20
+ * in `database-type-operations-controller.ts:1163`. The server pushes
21
+ * the full path (e.g. `"X.Y"` for `$params.X.Y`); the CLI extracts
22
+ * only the first segment (`"X"`). Net effect: the CLI validator is
23
+ * more lenient than the server — `$params.X.Y` passes the CLI check
24
+ * when `X` alone is declared in `[[operations.params]]`, even though
25
+ * the server treats the full path as the lookup key. The server
26
+ * remains authoritative; the CLI's first-segment extraction is a
27
+ * pragmatic choice so authors can declare structured params (e.g.
28
+ * `config: { type: "object" }`) and reference sub-fields like
29
+ * `$params.config.subKey` without listing every sub-field
30
+ * individually (review feedback r3246635661).
31
+ */
32
+ export interface ValidationIssue {
33
+ file: string;
34
+ line: number;
35
+ op: string;
36
+ message: string;
37
+ }
38
+ export interface ValidationResult {
39
+ errors: ValidationIssue[];
40
+ warnings: ValidationIssue[];
41
+ }
42
+ /**
43
+ * Walk an arbitrary JSON-ish value and collect `$params.X` references
44
+ * found in string-valued leaves, returning only the first dotted segment
45
+ * (`"X"` for `$params.X.Y`). Intentionally more lenient than the
46
+ * server-side helper, which pushes the full path — see the module
47
+ * doc-comment for the rationale.
48
+ */
49
+ export declare function collectParamRefs(value: any): string[];
50
+ /**
51
+ * Normalize a `params` value (object, array, or JSON string) into the
52
+ * set of declared param names.
53
+ */
54
+ export declare function declaredParamNames(params: any): Set<string>;
55
+ /**
56
+ * Find the 1-indexed line number in `rawToml` where the `[[operations]]`
57
+ * block declaring `opName` starts. Returns 0 if not found.
58
+ *
59
+ * Implementation: walk in source order, track the most recent
60
+ * `[[operations]]` header, and when we see a `name = "<opName>"` line
61
+ * before the next `[[operations]]` or top-level header, return that
62
+ * header's line.
63
+ */
64
+ export declare function locateOperationLine(rawToml: string, opName: string): number;
65
+ export interface ValidateOptions {
66
+ filePath: string;
67
+ rawToml: string;
68
+ /**
69
+ * Operations as parsed for the wire (definition/params already in JS
70
+ * object form). The validator works against the parsed shape so it's
71
+ * indifferent to native vs JSON-string source.
72
+ */
73
+ operations: Array<{
74
+ name: string;
75
+ definition: any;
76
+ params?: any;
77
+ }>;
78
+ }
79
+ /**
80
+ * Validate `$params` references across every operation in a parsed
81
+ * database-type config.
82
+ *
83
+ * Errors (blocking):
84
+ * - A `$params.X` reference inside `definition` where `X` is not
85
+ * declared in `[[operations.params]]`.
86
+ *
87
+ * Warnings (soft, not blocking):
88
+ * - A param declared in `[[operations.params]]` that is not referenced
89
+ * anywhere in `definition`. Authors may be staging a deprecation.
90
+ */
91
+ export declare function validateOperations(opts: ValidateOptions): ValidationResult;
92
+ /**
93
+ * Format a validation issue as `<file>:<line>: <message>`.
94
+ */
95
+ export declare function formatIssue(issue: ValidationIssue): string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Checks if a newer version of the CLI is available on npm.
3
+ *
4
+ * - Stable versions (e.g. "1.0.22") check the "latest" dist-tag
5
+ * - Prerelease versions (e.g. "1.0.23-alpha.3") check their channel's
6
+ * dist-tag (e.g. "alpha"), falling back to "latest" if that tag doesn't exist
7
+ *
8
+ * Caches results per dist-tag for 4 hours. Fails silently on any error.
9
+ */
10
+ export declare function checkForUpdate(currentVersion: string): Promise<void>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * CLI-only workflow fragment expansion.
3
+ *
4
+ * Workflows may declare `include = ["fragment-name", ...]` at the top of
5
+ * their TOML. The CLI expands those references into a fully-flattened
6
+ * `[[steps]]` list before push — the server never sees fragments and
7
+ * stores only the canonical, expanded JSON.
8
+ *
9
+ * Pinned decisions (#744):
10
+ * - CLI-only. No server-side WorkflowFragment model.
11
+ * - Fragments live at <workflowDir>/../workflow-fragments/<name>.toml.
12
+ * - Fragment files are `[[steps]]` lists only — no `[workflow]` block.
13
+ * - No recursive includes in v1 (fragments cannot themselves `include`).
14
+ * - Unique step ids validated post-expansion; collisions name both
15
+ * locations in the error message.
16
+ *
17
+ * Wiring: `parseTomlFile()` in `cli/src/commands/sync.ts` calls
18
+ * `expandWorkflowTomlData()` after parsing. All 24 push parse sites in
19
+ * sync.ts route through that single seam, so the expansion is uniform.
20
+ * The standalone `expandWorkflow(filePath)` helper backs the
21
+ * `primitive workflows expand` subcommand for debugging.
22
+ */
23
+ export interface ExpandedWorkflow {
24
+ workflow?: Record<string, any>;
25
+ steps: Array<Record<string, any>>;
26
+ include?: undefined;
27
+ [key: string]: any;
28
+ }
29
+ /**
30
+ * Read and expand a workflow TOML file from disk. Returns the parsed
31
+ * TOML with all `include` fragments spliced in. If the file has no
32
+ * `include` key, the parsed TOML is returned unchanged.
33
+ */
34
+ export declare function expandWorkflow(workflowPath: string): ExpandedWorkflow;
35
+ /**
36
+ * Expand the `include` key of an already-parsed TOML object. Resolves
37
+ * fragment files relative to `<workflowPath>/../../workflow-fragments/`.
38
+ * Use this when the TOML has already been parsed elsewhere (e.g. inside
39
+ * `parseTomlFile()`).
40
+ */
41
+ export declare function expandWorkflowTomlData(parsed: Record<string, any>, workflowPath: string): ExpandedWorkflow;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Push-time validator for workflow TOML files (issue #685).
3
+ *
4
+ * Detects the common footgun where a user writes `[steps.<id>.<field>]` under
5
+ * an open `[[steps]]` array. TOML parses that as a sub-table on the
6
+ * most-recent step keyed by the step's id (e.g. `steps[0]["refresh-each"]`),
7
+ * not as the intended `steps[0].request`. The result is a step with an
8
+ * unrecognized top-level field — the runtime silently ignores it, and the
9
+ * step then runs with an empty `request` block.
10
+ *
11
+ * The validator walks every step in `tomlData.steps[]` and reports any
12
+ * top-level field not in the universal-and-consumed allowlist. The allowlist
13
+ * is the union of:
14
+ * - Universal step fields declared on `BaseStepDefinition` in
15
+ * `src/workflows/runner/types.ts`
16
+ * - The top-level fields each step runner in `src/workflows/steps/`
17
+ * actually reads
18
+ *
19
+ * The list is universal-only (not per-kind) by design: the CLI doesn't know
20
+ * which step kinds exist on a given server version, and a per-kind list
21
+ * would couple the CLI tightly to the runtime. A universal allowlist
22
+ * catches the misnested-header footgun cleanly while staying tolerant of
23
+ * future step kinds that consume top-level fields already in the union.
24
+ *
25
+ * Maintenance: when a new step kind starts reading a new top-level field,
26
+ * add the field name to `ALLOWLISTED_FIELDS` below. See `cli/README.md`
27
+ * for the maintenance note.
28
+ */
29
+ /**
30
+ * Read-only view of `ALLOWLISTED_FIELDS` for the drift-guard test (#985).
31
+ *
32
+ * Exported so `cli/tests/unit/workflow-toml-validator-drift-guard.test.ts` can
33
+ * assert this set stays a superset of every top-level field the step runners
34
+ * in `src/workflows/steps/*.ts` actually read — checking the *live* set the
35
+ * validator enforces rather than a copy that could itself drift. Not part of
36
+ * the public validator API; consumed only by the guard.
37
+ */
38
+ export declare const ALLOWLISTED_FIELDS_FOR_DRIFT_GUARD: ReadonlySet<string>;
39
+ /**
40
+ * A single offending field on a step.
41
+ *
42
+ * `stepIndex` is the array index in `steps[]`.
43
+ * `stepId` is the value of the step's `id` field, if any (string).
44
+ * `field` is the unknown top-level key; uses sentinel names for shape
45
+ * errors:
46
+ * - `__steps_shape__`: `steps` is not an array (table or scalar).
47
+ * - `__step_shape__`: a step is not an object.
48
+ * `hint` is a short, user-facing explanation (used by
49
+ * `formatWorkflowTomlErrors` to render the multi-line diagnostic).
50
+ * `parentStepId` is set when this error came from a `collect` step's
51
+ * nested inner `step` definition (codex review, 2026-05-15): the outer
52
+ * `stepIndex` points at the array index, `parentStepId` carries the
53
+ * outer step's id (if any), and `stepId` carries the inner step's id.
54
+ * It's `null` for top-level errors (the renderer treats both `null` and
55
+ * `undefined` as top-level — see `formatWorkflowTomlErrors`).
56
+ */
57
+ export type WorkflowTomlError = {
58
+ stepIndex: number;
59
+ stepId: string | null;
60
+ field: string;
61
+ hint: string;
62
+ parentStepId?: string | null;
63
+ };
64
+ /**
65
+ * Walk a parsed workflow TOML document and return all unknown-field errors.
66
+ *
67
+ * @param tomlData The output of `TOML.parse()` for a workflow TOML file.
68
+ * @returns An array of error objects, one per offending field. Empty if no
69
+ * errors were found.
70
+ */
71
+ export declare function validateWorkflowToml(tomlData: any): WorkflowTomlError[];
72
+ /**
73
+ * Render a multi-line diagnostic for a list of errors. Designed for the
74
+ * shape called out in the issue:
75
+ *
76
+ * Error in workflows/refresh.toml:
77
+ * steps[0] (id="refresh-each") has unknown field "refresh-each".
78
+ * This is usually caused by writing [steps.refresh-each.request] instead of [steps.request].
79
+ * TOML can't address an array element by id — use [steps.request] (refers to the most recent step).
80
+ *
81
+ * @param filePath The TOML file path (or display name) to include in the
82
+ * header.
83
+ * @param errors The error list from `validateWorkflowToml`.
84
+ * @returns A multi-line string ready to print to stderr.
85
+ */
86
+ export declare function formatWorkflowTomlErrors(filePath: string, errors: WorkflowTomlError[]): string;