@backifydev/cli 0.2.0 → 0.2.2

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 (3) hide show
  1. package/README.md +14 -7
  2. package/dist/index.js +151 -29
  3. package/package.json +28 -28
package/README.md CHANGED
@@ -1,13 +1,20 @@
1
- # @backifydev/cli
2
-
3
- Set up and check a Backify application repository.
4
-
5
- ```sh
6
- npx @backifydev/cli init --app app_xxxxx
1
+ # @backifydev/cli
2
+
3
+ Set up and check a Backify application repository.
4
+
5
+ ```sh
6
+ npx @backifydev/cli init --app app_xxxxx
7
7
  npx @backifydev/cli doctor
8
8
  npx @backifydev/cli sync
9
+ npx @backifydev/cli login
9
10
  ```
10
11
 
12
+ `login` opens the Backify portal in your browser. Sign in and select an organization; the portal returns the organization and access token to the CLI through a random, short-lived callback bound to `127.0.0.1`. The CLI stores them in `~/.backify.json`. Set `portalUrl` in that file to use another portal, or `BACKIFY_PORTAL_URL` in the environment. `login <token> [organizationId]` remains available for scripted development.
13
+
11
14
  `init` installs `@backifydev/sdk`, writes `backify.json`, and maintains the Backify blocks in `AGENTS.md`, `CLAUDE.md`, and `.github/copilot-instructions.md`. It preserves other instructions in those files.
12
15
 
13
- See the [agent setup guide](https://docs.backify.dev/setup).
16
+ The app configuration stores `instructionVersion`, matching the version declared in each managed block and its immutable guide at `https://agent.backify.dev/v<number>`. The agent root always serves the latest guide as plain text. `doctor` detects version/content drift and malformed markers. Use `npx @backifydev/cli@latest sync` to explicitly upgrade all blocks and the configuration together; unrelated configuration and unmanaged instructions remain unchanged.
17
+
18
+ You can start from an empty folder without a Git repository. `init` creates a minimal private `package.json` before installing the SDK, without choosing a framework. Add the application's framework and development/build scripts afterward. Existing manifests are preserved; in a non-empty application or monorepo, run `init` from the appropriate JavaScript/TypeScript package directory.
19
+
20
+ See the [agent setup guide](https://agent.backify.dev/).
package/dist/index.js CHANGED
@@ -4,14 +4,17 @@
4
4
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, rmSync } from "node:fs";
5
5
  import { homedir } from "node:os";
6
6
  import { join as join2 } from "node:path";
7
+ import { randomBytes } from "node:crypto";
8
+ import { createServer } from "node:http";
9
+ import { spawn } from "node:child_process";
7
10
 
8
11
  // src/setup.ts
9
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
10
- import { dirname, join } from "node:path";
12
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
13
+ import { basename, dirname, join, resolve } from "node:path";
11
14
  import { spawnSync } from "node:child_process";
12
15
 
13
16
  // ../platform/dist/index.js
14
- var INSTRUCTION_VERSION = 1;
17
+ var INSTRUCTION_VERSION = 2;
15
18
  var SDK_PACKAGE = "@backifydev/sdk";
16
19
  var DOCS_URL = "https://docs.backify.dev";
17
20
  var capability = (name, imp, description, stateful = false, provisioner = name) => ({ name, import: imp, version: 1, stateful, provisioner, docs: `${DOCS_URL}/capabilities/${name}`, description, destructiveRemovalRequiresApproval: stateful });
@@ -38,25 +41,62 @@ function managedInstructions() {
38
41
 
39
42
  # Backify Platform Instructions
40
43
 
41
- This application runs on Backify.
44
+ Instruction version: ${INSTRUCTION_VERSION}
45
+ Versioned guide: https://agent.backify.dev/v${INSTRUCTION_VERSION}
42
46
 
43
- Use \`@backifydev/sdk\` for application platform capabilities. Prefer Backify for authentication, database, storage, server functions, background and scheduled jobs, events, secrets, AI, external HTTP access, audit logging, application logging, and configuration.
47
+ ## Scope and platform policy
44
48
 
45
- Do not directly provision or integrate Azure, AWS, GCP, Supabase, Firebase, Auth0, database hosting, object storage, queues, schedulers, or infrastructure-as-code when an equivalent Backify capability exists.
49
+ This application runs on Backify. These rules govern application code targeting Backify, not the implementation of the Backify platform itself.
50
+ Use \`@backifydev/sdk\` for platform capabilities. Do not directly integrate or provision cloud infrastructure, hosted authentication, databases, storage, queues, or schedulers when an equivalent Backify capability exists. If a capability is missing, explain the gap and ask before adding an external platform dependency.
46
51
 
47
- The authoritative documentation is ${DOCS_URL}. If unsure, check it before adding an external platform dependency. Preserve all repository instructions outside this managed block.
52
+ ## SDK capability map
53
+
54
+ - Authentication and authorization: \`auth\`.
55
+ - Persistent data and files: \`db\` and \`storage\`.
56
+ - Server functions: \`defineFunction\`; background work and schedules: \`jobs\` and \`jobs.schedule\`.
57
+ - Events, outbound HTTP, and AI: \`events\`, \`http.fetch\`, and \`ai\`.
58
+ - Server-side secrets, configuration, audit, and logs: \`secrets\`, \`config\`, \`audit\`, and \`logs\`.
59
+
60
+ Verify API signatures against the installed SDK types and the relevant guide at ${DOCS_URL}/capabilities/. Never invent SDK methods. Use \`@backifydev/sdk/local\` for tests and local development; \`@backifydev/sdk/runtime\` is for runtime hosts, not application code.
61
+
62
+ ## Security and data safety
63
+
64
+ Keep secrets server-side. Never commit or log credentials, expose secrets to browser code, or place cloud-management credentials in applications. Enforce authorization in trusted server code and scope data access to the authenticated user or organization as required.
65
+ Scope cookies to the exact application hostname by omitting the \`Domain\` attribute. Never use a parent-domain cookie unless explicitly required.
66
+ Require explicit confirmation before destructive data operations or removing stateful resources. Removing a capability declaration does not authorize deleting its data.
67
+
68
+ ## Implementation workflow
69
+
70
+ Inspect the framework, lockfile, scripts, configuration, and applicable repository instructions before editing. Preserve the existing framework and unrelated work. Implement the requested application rather than stopping at SDK setup; ask what to build if requirements are missing.
71
+ Run relevant tests, typechecking, lint, and builds using the repository's scripts when available. Report changes, checks, and remaining blockers accurately; do not claim unverified success.
72
+
73
+ ## Instruction maintenance
74
+
75
+ The authoritative documentation is ${DOCS_URL}/. The root at https://agent.backify.dev/ always shows the latest guide; use the versioned guide above for this repository's installed instructions.
76
+ Keep \`backify.json\` \`instructionVersion\` aligned with this block. Run \`npx @backifydev/cli@latest doctor\` to check consistency and \`npx @backifydev/cli@latest sync\` to adopt a newer instruction version. Never silently upgrade instructions during an unrelated feature change.
77
+ Preserve all content outside this managed block and all more-specific repository instructions.
48
78
 
49
79
  ${MANAGED_END}`;
50
80
  }
81
+ function managedInstructionIssues(content) {
82
+ const normalized = content.replace(/\r\n/g, "\n"), starts = normalized.split(MANAGED_START).length - 1, ends = normalized.split(MANAGED_END).length - 1;
83
+ if (starts === 0 && ends === 0)
84
+ return ["does not contain Backify managed instructions"];
85
+ if (starts !== 1 || ends !== 1)
86
+ return ["contains duplicate or incomplete Backify managed markers"];
87
+ const start = normalized.indexOf(MANAGED_START), end = normalized.indexOf(MANAGED_END);
88
+ if (end < start)
89
+ return ["contains reversed Backify managed markers"];
90
+ return normalized.slice(start, end + MANAGED_END.length) === managedInstructions() ? [] : ["contains stale or inconsistent Backify managed instructions; run backify sync"];
91
+ }
51
92
  function updateManagedInstructions(content) {
93
+ const issues = managedInstructionIssues(content);
94
+ if (issues.some((issue) => issue.includes("markers")))
95
+ throw new Error(issues.join("; "));
52
96
  const block = managedInstructions(), start = content.indexOf(MANAGED_START), end = content.indexOf(MANAGED_END);
53
- if (start >= 0 && end >= start)
97
+ if (start >= 0)
54
98
  return content.slice(0, start) + block + content.slice(end + MANAGED_END.length);
55
- return content.trim() ? `${content.trimEnd()}
56
-
57
- ${block}
58
- ` : `${block}
59
- `;
99
+ return content.trim() ? content.trimEnd() + "\n\n" + block + "\n" : block + "\n";
60
100
  }
61
101
  function createConfig(appId) {
62
102
  return { $schema: `${DOCS_URL}/schema/backify.json`, platform: "backify", sdk: SDK_PACKAGE, appId, instructionVersion: INSTRUCTION_VERSION, docs: DOCS_URL, instructions: { preferBackifyCapabilities: true, allowDirectCloudInfrastructure: false } };
@@ -89,12 +129,25 @@ function detectPackageManager(root = process.cwd()) {
89
129
  return "npm";
90
130
  }
91
131
  function updateInstructions(root = process.cwd()) {
92
- for (const relative of instructionFiles) {
132
+ const updates = instructionFiles.map((relative) => {
93
133
  const path = join(root, relative);
134
+ return { path, content: updateManagedInstructions(existsSync(path) ? readFileSync(path, "utf8") : "") };
135
+ });
136
+ for (const { path, content } of updates) {
94
137
  mkdirSync(dirname(path), { recursive: true });
95
- writeFileSync(path, updateManagedInstructions(existsSync(path) ? readFileSync(path, "utf8") : ""));
138
+ writeFileSync(path, content);
96
139
  }
97
140
  }
141
+ function synchronize(root = process.cwd()) {
142
+ const path = join(root, "backify.json");
143
+ if (!existsSync(path)) throw new Error("backify.json is missing; run backify init --app app_xxxxx");
144
+ const value = JSON.parse(readFileSync(path, "utf8")), issues = validateConfig(value);
145
+ if (issues.length) throw new Error(issues.join("; "));
146
+ if (value.instructionVersion > INSTRUCTION_VERSION) throw new Error("This app uses newer instructions. Install a matching or newer CLI before syncing.");
147
+ updateInstructions(root);
148
+ value.instructionVersion = INSTRUCTION_VERSION;
149
+ writeFileSync(path, JSON.stringify(value, null, 2) + "\n");
150
+ }
98
151
  function writeConfig(appId, root = process.cwd()) {
99
152
  writeFileSync(join(root, "backify.json"), JSON.stringify(createConfig(appId), null, 2) + "\n");
100
153
  }
@@ -111,9 +164,17 @@ function installSdk(root = process.cwd()) {
111
164
  const manager = detectPackageManager(root), args2 = manager === "npm" ? ["install", SDK_PACKAGE] : ["add", SDK_PACKAGE], result = spawnSync(manager, args2, { cwd: root, stdio: "inherit", shell: process.platform === "win32" });
112
165
  if (result.status !== 0) throw new Error(`Could not install ${SDK_PACKAGE} with ${manager}`);
113
166
  }
167
+ function ensurePackageManifest(root) {
168
+ const path = join(root, "package.json");
169
+ if (existsSync(path)) return;
170
+ const setupFiles = /* @__PURE__ */ new Set([".git", ".gitignore", ".github", "AGENTS.md", "CLAUDE.md", "README.md", "LICENSE", "backify.json"]);
171
+ if (readdirSync(root).some((file) => !setupFiles.has(file))) throw new Error("package.json is missing. Run backify init from the existing application's JavaScript/TypeScript package directory, or start in an empty folder. See https://agent.backify.dev/");
172
+ const name = basename(resolve(root)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "backify-app";
173
+ writeFileSync(path, JSON.stringify({ name, version: "0.0.0", private: true, type: "module" }, null, 2) + "\n", { flag: "wx" });
174
+ }
114
175
  function initialize(appId, root = process.cwd(), install = true) {
115
176
  if (!/^app_[A-Za-z0-9_-]+$/.test(appId)) throw new Error("Provide an application ID using --app app_xxxxx");
116
- if (!existsSync(join(root, "package.json"))) throw new Error("Run backify init in a JavaScript/TypeScript repository containing package.json");
177
+ ensurePackageManifest(root);
117
178
  if (install) installSdk(root);
118
179
  writeConfig(appId, root);
119
180
  updateInstructions(root);
@@ -128,15 +189,14 @@ function doctor(root = process.cwd()) {
128
189
  } catch {
129
190
  issues.push("backify.json is missing or invalid JSON");
130
191
  }
131
- if (config?.instructionVersion < INSTRUCTION_VERSION) warnings.push(`Repository instruction version ${config.instructionVersion}; current version ${INSTRUCTION_VERSION}. Run backify sync.`);
192
+ if (config && config.instructionVersion !== INSTRUCTION_VERSION) issues.push(`Repository instruction version ${config.instructionVersion}; CLI version ${INSTRUCTION_VERSION}. Use the matching CLI or run backify sync to upgrade.`);
132
193
  for (const relative of instructionFiles) {
133
194
  const path = join(root, relative);
134
195
  if (!existsSync(path)) {
135
196
  issues.push(`${relative} is missing`);
136
197
  continue;
137
198
  }
138
- const text = readFileSync(path, "utf8");
139
- if (!text.includes(MANAGED_START) || !text.includes(MANAGED_END)) issues.push(`${relative} does not contain Backify managed instructions`);
199
+ issues.push(...managedInstructionIssues(readFileSync(path, "utf8")).map((issue) => `${relative} ${issue}`));
140
200
  }
141
201
  let version = "unknown";
142
202
  try {
@@ -160,6 +220,67 @@ var option = (name) => {
160
220
  const index = args.indexOf(name);
161
221
  return index >= 0 ? args[index + 1] : void 0;
162
222
  };
223
+ function openBrowser(url) {
224
+ const command2 = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open", args2 = process.platform === "win32" ? ["/c", "start", "", '"' + url + '"'] : [url];
225
+ const child = spawn(command2, args2, { stdio: "ignore", detached: true, shell: process.platform === "win32" });
226
+ child.unref();
227
+ }
228
+ async function browserLogin() {
229
+ const state = randomBytes(24).toString("base64url"), server = createServer((req, res) => {
230
+ if (req.method === "OPTIONS") {
231
+ res.writeHead(204, { "access-control-allow-origin": "*", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-headers": "content-type" });
232
+ return res.end();
233
+ }
234
+ if (req.method !== "POST" || req.url !== "/callback") {
235
+ res.writeHead(404);
236
+ return res.end();
237
+ }
238
+ let raw = "";
239
+ req.setEncoding("utf8");
240
+ req.on("data", (part) => raw += part);
241
+ req.on("end", () => {
242
+ try {
243
+ const value = JSON.parse(raw);
244
+ if (value.state !== state || typeof value.token !== "string" || typeof value.organizationId !== "string") throw new Error("Invalid callback");
245
+ res.writeHead(200, { "access-control-allow-origin": "*", "content-type": "text/plain" });
246
+ res.end("Backify CLI connected. You can close this tab.");
247
+ userConfig.token = value.token;
248
+ userConfig.organizationId = value.organizationId;
249
+ writeFileSync2(configPath, JSON.stringify(userConfig, null, 2));
250
+ server.close();
251
+ resolveLogin();
252
+ } catch {
253
+ res.writeHead(400, { "access-control-allow-origin": "*" });
254
+ res.end("Invalid or expired Backify CLI login.");
255
+ }
256
+ });
257
+ });
258
+ let resolveLogin = () => {
259
+ };
260
+ const completed = new Promise((resolve2) => resolveLogin = resolve2);
261
+ server.listen(0, "127.0.0.1", () => {
262
+ const address = server.address();
263
+ if (!address || typeof address === "string") throw new Error("Could not start local login callback");
264
+ const callback = `http://127.0.0.1:${address.port}/callback`;
265
+ const portal = new URL(process.env.BACKIFY_PORTAL_URL ?? userConfig.portalUrl ?? "https://backify.dev");
266
+ portal.searchParams.set("cli_callback", callback);
267
+ portal.searchParams.set("cli_state", state);
268
+ console.log(`Opening ${portal.origin} to sign in to Backify\u2026`);
269
+ openBrowser(portal.toString());
270
+ });
271
+ const timeout = setTimeout(() => {
272
+ server.close();
273
+ rejectLogin(new Error("Backify login timed out. Run backify login to try again."));
274
+ }, 5 * 6e4);
275
+ let rejectLogin = () => {
276
+ };
277
+ const failed = new Promise((_, reject) => rejectLogin = reject);
278
+ try {
279
+ await Promise.race([completed, failed]);
280
+ } finally {
281
+ clearTimeout(timeout);
282
+ }
283
+ }
163
284
  async function main() {
164
285
  switch (command) {
165
286
  case "init":
@@ -167,12 +288,7 @@ async function main() {
167
288
  console.log("Backify initialized. SDK, configuration, and agent instructions are ready.");
168
289
  break;
169
290
  case "sync": {
170
- const path = join2(process.cwd(), "backify.json");
171
- if (!existsSync2(path)) throw new Error("backify.json is missing; run backify init --app app_xxxxx");
172
- const value = JSON.parse(readFileSync2(path, "utf8"));
173
- value.instructionVersion = INSTRUCTION_VERSION;
174
- writeFileSync2(path, JSON.stringify(value, null, 2) + "\n");
175
- updateInstructions();
291
+ synchronize();
176
292
  console.log(`Backify instructions synchronized to version ${INSTRUCTION_VERSION}.`);
177
293
  break;
178
294
  }
@@ -186,10 +302,16 @@ async function main() {
186
302
  break;
187
303
  }
188
304
  case "login":
189
- userConfig.token = args[0] ?? "development";
190
- userConfig.organizationId = args[1] ?? userConfig.organizationId;
191
- writeFileSync2(configPath, JSON.stringify(userConfig, null, 2));
192
- console.log("Logged in to Backify");
305
+ if (args[0]) {
306
+ userConfig.token = args[0];
307
+ userConfig.organizationId = args[1] ?? userConfig.organizationId;
308
+ writeFileSync2(configPath, JSON.stringify(userConfig, null, 2));
309
+ console.log("Logged in to Backify");
310
+ } else {
311
+ await browserLogin();
312
+ console.log("Logged in to Backify");
313
+ }
314
+ ;
193
315
  break;
194
316
  case "logout":
195
317
  rmSync(configPath, { force: true });
package/package.json CHANGED
@@ -1,29 +1,29 @@
1
- {
2
- "name": "@backifydev/cli",
3
- "version": "0.2.0",
4
- "description": "Command-line setup and diagnostics for Backify applications",
5
- "type": "module",
6
- "bin": { "backify": "./dist/index.js" },
7
- "files": ["dist/index.js", "README.md"],
8
- "scripts": {
9
- "prebuild": "npm run build --workspace @backify/platform",
10
- "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js",
11
- "prepack": "npm run build",
12
- "pretypecheck": "npm run build --workspace @backify/platform",
13
- "typecheck": "tsc --noEmit",
14
- "pretest": "npm run build --workspace @backify/platform",
15
- "test": "vitest run"
16
- },
17
- "publishConfig": { "access": "public" },
1
+ {
2
+ "name": "@backifydev/cli",
3
+ "version": "0.2.2",
4
+ "description": "Command-line setup and diagnostics for Backify applications",
5
+ "type": "module",
6
+ "bin": { "backify": "dist/index.js" },
7
+ "files": ["dist/index.js", "README.md"],
8
+ "scripts": {
9
+ "prebuild": "npm run build --workspace @backify/platform",
10
+ "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js",
11
+ "prepack": "npm run build",
12
+ "pretypecheck": "npm run build --workspace @backify/platform",
13
+ "typecheck": "tsc --noEmit",
14
+ "pretest": "npm run build --workspace @backify/platform",
15
+ "test": "vitest run"
16
+ },
17
+ "publishConfig": { "access": "public" },
18
18
  "repository": { "type": "git", "url": "git+https://github.com/frejnorling/backify.git", "directory": "packages/cli" },
19
- "homepage": "https://docs.backify.dev/setup",
20
- "license": "MIT",
21
- "engines": { "node": ">=20" },
22
- "devDependencies": {
23
- "@backify/platform": "*",
24
- "@types/node": "^24.3.1",
25
- "esbuild": "^0.25.10",
26
- "typescript": "^5.9.2",
27
- "vitest": "^3.2.4"
28
- }
29
- }
19
+ "homepage": "https://agent.backify.dev/",
20
+ "license": "MIT",
21
+ "engines": { "node": ">=20" },
22
+ "devDependencies": {
23
+ "@backify/platform": "*",
24
+ "@types/node": "^24.3.1",
25
+ "esbuild": "^0.25.10",
26
+ "typescript": "^5.9.2",
27
+ "vitest": "^4.1.11"
28
+ }
29
+ }