@polpo-ai/cli 0.6.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.
Files changed (68) hide show
  1. package/LICENSE +13 -0
  2. package/dist/commands/cloud/api.js +32 -0
  3. package/dist/commands/cloud/api.js.map +1 -0
  4. package/dist/commands/cloud/byok.js +125 -0
  5. package/dist/commands/cloud/byok.js.map +1 -0
  6. package/dist/commands/cloud/config.js +42 -0
  7. package/dist/commands/cloud/config.js.map +1 -0
  8. package/dist/commands/cloud/deploy.js +752 -0
  9. package/dist/commands/cloud/deploy.js.map +1 -0
  10. package/dist/commands/cloud/login.js +99 -0
  11. package/dist/commands/cloud/login.js.map +1 -0
  12. package/dist/commands/cloud/logout.js +11 -0
  13. package/dist/commands/cloud/logout.js.map +1 -0
  14. package/dist/commands/cloud/logs.js +114 -0
  15. package/dist/commands/cloud/logs.js.map +1 -0
  16. package/dist/commands/cloud/project-context.js +17 -0
  17. package/dist/commands/cloud/project-context.js.map +1 -0
  18. package/dist/commands/cloud/projects.js +95 -0
  19. package/dist/commands/cloud/projects.js.map +1 -0
  20. package/dist/commands/cloud/prompt.js +74 -0
  21. package/dist/commands/cloud/prompt.js.map +1 -0
  22. package/dist/commands/cloud/status.js +84 -0
  23. package/dist/commands/cloud/status.js.map +1 -0
  24. package/dist/commands/create.js +286 -0
  25. package/dist/commands/create.js.map +1 -0
  26. package/dist/commands/link.js +71 -0
  27. package/dist/commands/link.js.map +1 -0
  28. package/dist/commands/models.js +60 -0
  29. package/dist/commands/models.js.map +1 -0
  30. package/dist/commands/orgs.js +42 -0
  31. package/dist/commands/orgs.js.map +1 -0
  32. package/dist/commands/update.js +95 -0
  33. package/dist/commands/update.js.map +1 -0
  34. package/dist/commands/whoami.js +62 -0
  35. package/dist/commands/whoami.js.map +1 -0
  36. package/dist/index.js +138 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/interactive-menu.js +111 -0
  39. package/dist/interactive-menu.js.map +1 -0
  40. package/dist/update-check.js +106 -0
  41. package/dist/update-check.js.map +1 -0
  42. package/dist/util/api-keys.js +21 -0
  43. package/dist/util/api-keys.js.map +1 -0
  44. package/dist/util/auth.js +71 -0
  45. package/dist/util/auth.js.map +1 -0
  46. package/dist/util/base-url.js +43 -0
  47. package/dist/util/base-url.js.map +1 -0
  48. package/dist/util/browser.js +20 -0
  49. package/dist/util/browser.js.map +1 -0
  50. package/dist/util/device-code.js +103 -0
  51. package/dist/util/device-code.js.map +1 -0
  52. package/dist/util/errors.js +13 -0
  53. package/dist/util/errors.js.map +1 -0
  54. package/dist/util/install-cli.js +68 -0
  55. package/dist/util/install-cli.js.map +1 -0
  56. package/dist/util/org.js +53 -0
  57. package/dist/util/org.js.map +1 -0
  58. package/dist/util/polpo-config.js +39 -0
  59. package/dist/util/polpo-config.js.map +1 -0
  60. package/dist/util/project.js +87 -0
  61. package/dist/util/project.js.map +1 -0
  62. package/dist/util/skills.js +53 -0
  63. package/dist/util/skills.js.map +1 -0
  64. package/dist/util/slugify.js +19 -0
  65. package/dist/util/slugify.js.map +1 -0
  66. package/dist/util/template.js +118 -0
  67. package/dist/util/template.js.map +1 -0
  68. package/package.json +38 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Device-code browser login flow.
3
+ *
4
+ * Used by both `polpo login` and the smart `requireAuth()` helper to
5
+ * trigger an interactive OAuth-style login when creds are missing.
6
+ *
7
+ * Flow:
8
+ * 1. POST /v1/cli-auth/request → returns { code, expiresAt }
9
+ * 2. Open `{dashboard}/cli-auth?code=<code>` in the user's browser
10
+ * 3. Poll /v1/cli-auth/poll/{code} until approved or expired
11
+ * 4. Save creds to ~/.polpo/credentials.json on success
12
+ *
13
+ * Does NOT retry automatically. Callers (requireAuth) can wrap in a
14
+ * retry loop if they want the user to try again on failure.
15
+ */
16
+ import pc from "picocolors";
17
+ import { openBrowser } from "./browser.js";
18
+ import { saveCredentials } from "../commands/cloud/config.js";
19
+ const DEFAULT_API_URL = "https://api.polpo.sh";
20
+ const DEFAULT_DASHBOARD_URL = "https://polpo.sh";
21
+ function sleep(ms) {
22
+ return new Promise((resolve) => setTimeout(resolve, ms));
23
+ }
24
+ export class DeviceCodeError extends Error {
25
+ reason;
26
+ constructor(message, reason) {
27
+ super(message);
28
+ this.reason = reason;
29
+ this.name = "DeviceCodeError";
30
+ }
31
+ }
32
+ /**
33
+ * Run the device-code login flow end-to-end.
34
+ *
35
+ * Resolves with the saved credentials on success.
36
+ * Throws `DeviceCodeError` on failure (network, expired, timeout).
37
+ */
38
+ export async function performDeviceCodeLogin(opts = {}) {
39
+ const baseUrl = opts.apiUrl ?? DEFAULT_API_URL;
40
+ const dashboardUrl = opts.dashboardUrl ?? DEFAULT_DASHBOARD_URL;
41
+ // 1. Request a code
42
+ let code;
43
+ let expiresAt;
44
+ try {
45
+ const res = await fetch(`${baseUrl}/v1/cli-auth/request`, {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json" },
48
+ });
49
+ if (!res.ok) {
50
+ throw new DeviceCodeError(`Server returned ${res.status}. Is ${baseUrl} reachable?`, "server");
51
+ }
52
+ const data = (await res.json());
53
+ code = data.code;
54
+ expiresAt = data.expiresAt;
55
+ }
56
+ catch (err) {
57
+ if (err instanceof DeviceCodeError)
58
+ throw err;
59
+ throw new DeviceCodeError(`Could not reach the API at ${baseUrl}: ${err.message}`, "network");
60
+ }
61
+ // 2. Prompt + open browser
62
+ console.log(`\n Authorization code: ${pc.bold(code)}\n`);
63
+ const authUrl = `${dashboardUrl}/cli-auth?code=${code}`;
64
+ if (opts.noBrowser) {
65
+ console.log(` Open this URL to authorize:\n ${authUrl}\n`);
66
+ }
67
+ else {
68
+ console.log(" Opening browser...");
69
+ console.log(pc.dim(` If it doesn't open, visit: ${authUrl}\n`));
70
+ await openBrowser(authUrl);
71
+ }
72
+ process.stdout.write(" Waiting for authorization...");
73
+ // 3. Poll
74
+ const expiry = new Date(expiresAt).getTime();
75
+ const POLL_MS = 2000;
76
+ while (Date.now() < expiry) {
77
+ await sleep(POLL_MS);
78
+ try {
79
+ const res = await fetch(`${baseUrl}/v1/cli-auth/poll/${code}`);
80
+ if (res.status === 404) {
81
+ throw new DeviceCodeError("Code expired.", "expired");
82
+ }
83
+ const data = (await res.json());
84
+ if (data.status === "approved" && data.token) {
85
+ saveCredentials(data.token, baseUrl);
86
+ console.log(pc.green("\n\n Logged in successfully."));
87
+ console.log(pc.dim(` Base URL: ${baseUrl}\n`));
88
+ return { apiKey: data.token, baseUrl };
89
+ }
90
+ if (data.status === "expired") {
91
+ throw new DeviceCodeError("Code expired.", "expired");
92
+ }
93
+ process.stdout.write(".");
94
+ }
95
+ catch (err) {
96
+ if (err instanceof DeviceCodeError)
97
+ throw err;
98
+ // Network blip — retry silently
99
+ }
100
+ }
101
+ throw new DeviceCodeError("Timed out waiting for authorization.", "timeout");
102
+ }
103
+ //# sourceMappingURL=device-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../src/util/device-code.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Translate raw API error messages to actionable CLI hints.
3
+ */
4
+ export function friendlyError(msg) {
5
+ if (msg.includes("Multiple projects found"))
6
+ return "Multiple projects found. Run: polpo projects set";
7
+ if (/HTTP 401|Unauthorized/i.test(msg))
8
+ return "Session expired or invalid. Run: polpo login";
9
+ if (/HTTP 403|Forbidden/i.test(msg))
10
+ return "Access denied. Check your credentials or project permissions.";
11
+ return msg;
12
+ }
13
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/util/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IAAI,GAAG,CAAC,QAAQ,CAAC,yBAAyB,CAAC;QAAE,OAAO,kDAAkD,CAAC;IACvG,IAAI,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,8CAA8C,CAAC;IAC9F,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,+DAA+D,CAAC;IAC5G,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Detect + install the `@polpo-ai/cli` package globally.
3
+ *
4
+ * Called at the end of `polpo create`: if the user ran via
5
+ * `npx @polpo-ai/cli create` they DON'T have the `polpo` bin on PATH
6
+ * after the wizard ends — they'd have to keep using `npx`. Offering a
7
+ * one-shot global install saves them from that.
8
+ *
9
+ * Skipped when `polpo` is already on PATH (user already installed it).
10
+ */
11
+ import { exec, execSync } from "node:child_process";
12
+ import { promisify } from "node:util";
13
+ const execAsync = promisify(exec);
14
+ export const CLI_PACKAGE = "@polpo-ai/cli";
15
+ /** `true` when the `polpo` bin is already on PATH globally. */
16
+ export function isPolpoOnPath() {
17
+ try {
18
+ const cmd = process.platform === "win32" ? "where polpo" : "which polpo";
19
+ execSync(cmd, { stdio: "ignore" });
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ /**
27
+ * Detect the user's preferred package manager from `npm_config_user_agent`
28
+ * so we install via the same tool they launched the wizard with.
29
+ * Falls back to `npm`.
30
+ */
31
+ export function detectPackageManager() {
32
+ const ua = process.env.npm_config_user_agent ?? "";
33
+ if (ua.includes("pnpm"))
34
+ return "pnpm";
35
+ if (ua.includes("yarn"))
36
+ return "yarn";
37
+ if (ua.includes("bun"))
38
+ return "bun";
39
+ return "npm";
40
+ }
41
+ function globalInstallCommand(pm) {
42
+ switch (pm) {
43
+ case "pnpm": return `pnpm add -g ${CLI_PACKAGE}`;
44
+ case "yarn": return `yarn global add ${CLI_PACKAGE}`;
45
+ case "bun": return `bun add -g ${CLI_PACKAGE}`;
46
+ default: return `npm install -g ${CLI_PACKAGE}`;
47
+ }
48
+ }
49
+ /**
50
+ * Run the global install. Never throws — returns `false` on any failure
51
+ * (permissions, offline, pkg manager not on PATH). Callers should log
52
+ * the failure + show the manual command as fallback.
53
+ */
54
+ export async function installPolpoGlobally() {
55
+ const pm = detectPackageManager();
56
+ const command = globalInstallCommand(pm);
57
+ try {
58
+ await execAsync(command, { timeout: 120_000, maxBuffer: 10 * 1024 * 1024 });
59
+ return { ok: true, pm, command };
60
+ }
61
+ catch {
62
+ return { ok: false, pm, command };
63
+ }
64
+ }
65
+ export function globalInstallHint() {
66
+ return globalInstallCommand(detectPackageManager());
67
+ }
68
+ //# sourceMappingURL=install-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-cli.js","sourceRoot":"","sources":["../../src/util/install-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAElC,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC;AAE3C,+DAA+D;AAC/D,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC;QACzE,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC;IACnD,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,oBAAoB,CAAC,EAA2C;IACvE,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,MAAM,CAAC,CAAC,OAAO,eAAe,WAAW,EAAE,CAAC;QACjD,KAAK,MAAM,CAAC,CAAC,OAAO,mBAAmB,WAAW,EAAE,CAAC;QACrD,KAAK,KAAK,CAAC,CAAE,OAAO,cAAc,WAAW,EAAE,CAAC;QAChD,OAAO,CAAC,CAAK,OAAO,kBAAkB,WAAW,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,MAAM,EAAE,GAAG,oBAAoB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,oBAAoB,CAAC,EAAE,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;QAC5E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;IACpC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,oBAAoB,CAAC,oBAAoB,EAAE,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Organization resolution helpers.
3
+ *
4
+ * `resolveDefaultOrg()` fetches the user's orgs from the control plane
5
+ * and returns the first one (used by `deploy` today where only one is
6
+ * expected). `pickOrg()` is the interactive variant for `create` where
7
+ * the user may belong to multiple orgs.
8
+ */
9
+ import * as clack from "@clack/prompts";
10
+ async function listOrgs(client) {
11
+ const res = await client.get("/v1/orgs");
12
+ return Array.isArray(res.data) ? res.data : [];
13
+ }
14
+ /**
15
+ * Return the first (and typically only) org.
16
+ * Throws when the user has no orgs — onboarding should have seeded one.
17
+ */
18
+ export async function resolveDefaultOrg(client) {
19
+ const orgs = await listOrgs(client);
20
+ if (orgs.length === 0) {
21
+ throw new Error("No organization found. Complete onboarding at polpo.sh first.");
22
+ }
23
+ return orgs[0];
24
+ }
25
+ /**
26
+ * Interactive org picker.
27
+ * - 0 orgs → throws
28
+ * - 1 org → returns it without prompting
29
+ * - >1 → clack.select picker
30
+ */
31
+ export async function pickOrg(client) {
32
+ const orgs = await listOrgs(client);
33
+ if (orgs.length === 0) {
34
+ throw new Error("No organization found. Complete onboarding at polpo.sh first.");
35
+ }
36
+ if (orgs.length === 1) {
37
+ clack.log.info(`Organization: ${orgs[0].name}`);
38
+ return orgs[0];
39
+ }
40
+ const choice = await clack.select({
41
+ message: "Select an organization:",
42
+ options: orgs.map((o) => ({ value: o.id, label: o.name })),
43
+ });
44
+ if (clack.isCancel(choice)) {
45
+ clack.cancel("Cancelled.");
46
+ process.exit(0);
47
+ }
48
+ const selected = orgs.find((o) => o.id === choice);
49
+ if (!selected)
50
+ throw new Error("Invalid org selection");
51
+ return selected;
52
+ }
53
+ //# sourceMappingURL=org.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org.js","sourceRoot":"","sources":["../../src/util/org.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AASxC,KAAK,UAAU,QAAQ,CAAC,MAAiB;IACvC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,CAAQ,UAAU,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAiB;IACvD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,MAAiB;IAC7C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAS;QACxC,OAAO,EAAE,yBAAyB;QAClC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;KAC3D,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;IACnD,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACxD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Read/write helpers for `.polpo/polpo.json` — the per-project config file
3
+ * that links a directory to a cloud project.
4
+ *
5
+ * Schema kept minimal on purpose — whatever fields exist already are
6
+ * preserved on partial updates.
7
+ */
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ export function polpoDirPath(cwd) {
11
+ return path.resolve(cwd, ".polpo");
12
+ }
13
+ export function polpoConfigPath(cwd) {
14
+ return path.join(polpoDirPath(cwd), "polpo.json");
15
+ }
16
+ export function readPolpoConfig(cwd) {
17
+ const file = polpoConfigPath(cwd);
18
+ if (!fs.existsSync(file))
19
+ return null;
20
+ try {
21
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ /**
28
+ * Merge `patch` into the existing `.polpo/polpo.json`, creating the file
29
+ * (and the `.polpo/` dir) if it doesn't exist yet.
30
+ */
31
+ export function writePolpoConfig(cwd, patch) {
32
+ const dir = polpoDirPath(cwd);
33
+ if (!fs.existsSync(dir))
34
+ fs.mkdirSync(dir, { recursive: true });
35
+ const existing = readPolpoConfig(cwd) ?? {};
36
+ const merged = { ...existing, ...patch };
37
+ fs.writeFileSync(polpoConfigPath(cwd), JSON.stringify(merged, null, 2) + "\n", "utf-8");
38
+ }
39
+ //# sourceMappingURL=polpo-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polpo-config.js","sourceRoot":"","sources":["../../src/util/polpo-config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AA0BlC,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAuB,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW,EAAE,KAAyB;IACrE,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC;IACzC,EAAE,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAC1F,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Project resolution + creation helpers.
3
+ *
4
+ * Centralises the "list projects → pick one, or create a new one" flow
5
+ * that both `polpo deploy` (today) and `polpo create` (incoming) need.
6
+ */
7
+ import * as clack from "@clack/prompts";
8
+ import { slugify } from "./slugify.js";
9
+ export async function listProjects(client, orgId) {
10
+ const res = await client.get(`/v1/projects?orgId=${orgId}`);
11
+ return Array.isArray(res.data) ? res.data : [];
12
+ }
13
+ export async function getProject(client, projectId) {
14
+ const res = await client.get(`/v1/projects/${projectId}`);
15
+ if (res.status === 404)
16
+ return null;
17
+ return (res.data ?? null);
18
+ }
19
+ export async function createProject(client, opts) {
20
+ const slug = opts.slug ?? slugify(opts.name);
21
+ const res = await client.post("/v1/projects", {
22
+ name: opts.name,
23
+ slug,
24
+ orgId: opts.orgId,
25
+ });
26
+ if (!res.data?.id) {
27
+ throw new Error(`Failed to create project: ${res.data?.error ?? `HTTP ${res.status}`}`);
28
+ }
29
+ return res.data;
30
+ }
31
+ /**
32
+ * Poll `GET /v1/projects/{id}` until `status === "active"` (or timeout).
33
+ * Projects usually become active within 30–60s of creation.
34
+ */
35
+ export async function waitForProjectActive(client, projectId, timeoutMs = 120_000) {
36
+ const start = Date.now();
37
+ while (Date.now() - start < timeoutMs) {
38
+ const project = await getProject(client, projectId);
39
+ if (project?.status === "active")
40
+ return project;
41
+ await new Promise((r) => setTimeout(r, 3000));
42
+ }
43
+ throw new Error("Project creation timed out. Check the dashboard for status.");
44
+ }
45
+ /**
46
+ * Canonical flow used by `polpo deploy` when no projectId is set locally:
47
+ * - 0 projects → create one (prompts unless `force`)
48
+ * - 1 project → auto-select, log, return
49
+ * - >1 → picker if interactive; error if not
50
+ */
51
+ export async function resolveOrCreateProject(opts) {
52
+ const { client, orgId, name, force, interactive } = opts;
53
+ const projects = await listProjects(client, orgId);
54
+ if (projects.length === 1) {
55
+ return projects[0];
56
+ }
57
+ if (projects.length > 1) {
58
+ if (!interactive) {
59
+ throw new Error("Multiple projects found. Set projectId in .polpo/polpo.json or run interactively.");
60
+ }
61
+ const choice = await clack.select({
62
+ message: "Select a project:",
63
+ options: projects.map((p) => ({ value: p.id, label: p.name })),
64
+ });
65
+ if (clack.isCancel(choice)) {
66
+ clack.cancel("Cancelled.");
67
+ process.exit(0);
68
+ }
69
+ const selected = projects.find((p) => p.id === choice);
70
+ if (!selected)
71
+ throw new Error("Invalid project selection");
72
+ return selected;
73
+ }
74
+ // Zero projects — create one.
75
+ if (!force && interactive) {
76
+ const ok = await clack.confirm({
77
+ message: `No projects found. Create "${name}"?`,
78
+ initialValue: true,
79
+ });
80
+ if (clack.isCancel(ok) || !ok) {
81
+ clack.cancel("Aborted.");
82
+ process.exit(0);
83
+ }
84
+ }
85
+ return await createProject(client, { name, orgId });
86
+ }
87
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/util/project.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Install the `lumea-labs/polpo-skills` rule pack for coding agents
3
+ * (Cursor, Claude Code, Windsurf, Codex, Roo, etc.) via the canonical
4
+ * `skills` CLI.
5
+ *
6
+ * Rule packs teach the user's coding agent how to work with Polpo
7
+ * (agents.json schema, `polpo deploy` flow, playbook YAML conventions,
8
+ * ...). They are additive: the `skills` tool merges into existing
9
+ * agent configs rather than replacing them.
10
+ *
11
+ * Scope:
12
+ * - `global` → user's machine (~/.claude, ~/.cursor, etc.)
13
+ * best when the user works on multiple Polpo projects
14
+ * - `project` → current directory (.claude/, .cursor/, ...)
15
+ * best for repos that want self-contained agent rules
16
+ * - `skip` → do nothing, we just print the one-liner in the outro
17
+ */
18
+ import { exec } from "node:child_process";
19
+ import { promisify } from "node:util";
20
+ const execAsync = promisify(exec);
21
+ export const POLPO_SKILLS_REPO = "lumea-labs/polpo-skills";
22
+ /**
23
+ * Shell out to `npx skills@latest add lumea-labs/polpo-skills [-g] -y`.
24
+ *
25
+ * Returns `true` on success, `false` on failure. We never throw — skills
26
+ * install is an enhancement, not a prerequisite. Callers should log
27
+ * the failure as a warning and continue.
28
+ */
29
+ export async function installCodingAgentSkills(opts) {
30
+ if (opts.scope === "skip")
31
+ return false;
32
+ const flags = [
33
+ "--yes", // non-interactive across all prompts inside `skills`
34
+ ];
35
+ if (opts.scope === "global")
36
+ flags.push("--global");
37
+ const cmd = `npx --yes skills@latest add ${POLPO_SKILLS_REPO} ${flags.join(" ")}`;
38
+ try {
39
+ await execAsync(cmd, {
40
+ cwd: opts.cwd ?? process.cwd(),
41
+ timeout: opts.timeoutMs ?? 90_000,
42
+ maxBuffer: 10 * 1024 * 1024,
43
+ });
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ export function skillsInstallHint() {
51
+ return `npx skills@latest add ${POLPO_SKILLS_REPO} --global`;
52
+ }
53
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/util/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAElC,MAAM,CAAC,MAAM,iBAAiB,GAAG,yBAAyB,CAAC;AAY3D;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,IAA0B;IACvE,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAExC,MAAM,KAAK,GAAG;QACZ,OAAO,EAAE,qDAAqD;KAC/D,CAAC;IACF,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEpD,MAAM,GAAG,GAAG,+BAA+B,iBAAiB,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAElF,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,GAAG,EAAE;YACnB,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;YAC9B,OAAO,EAAE,IAAI,CAAC,SAAS,IAAI,MAAM;YACjC,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;SAC5B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,yBAAyB,iBAAiB,WAAW,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Convert a human-readable project name into a URL-safe slug.
3
+ * Shared between `polpo create` and `polpo deploy` auto-create so slugs
4
+ * produced from the same name are identical.
5
+ *
6
+ * Rules:
7
+ * - lowercase
8
+ * - any non a-z0-9 collapses to a single hyphen
9
+ * - leading/trailing hyphens stripped
10
+ * - empty result → "project"
11
+ */
12
+ export function slugify(name) {
13
+ const slug = name
14
+ .toLowerCase()
15
+ .replace(/[^a-z0-9]+/g, "-")
16
+ .replace(/^-+|-+$/g, "");
17
+ return slug || "project";
18
+ }
19
+ //# sourceMappingURL=slugify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.js","sourceRoot":"","sources":["../../src/util/slugify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,IAAI;SACd,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,IAAI,IAAI,SAAS,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Template system for `polpo create`.
3
+ *
4
+ * Scaffolding is delegated to the separately-published `create-polpo-app`
5
+ * tool (maintained in the polpo-ui repo) — we shell out to it with flags
6
+ * so template logic + download strategy live in one place. Our CLI keeps
7
+ * only the metadata needed to render the wizard picker.
8
+ *
9
+ * For the `empty` option we scaffold a tiny `.polpo/` inline (no network,
10
+ * no external tool) because `create-polpo-app` targets full frontend
11
+ * templates, not blank projects.
12
+ */
13
+ import * as fs from "node:fs";
14
+ import * as path from "node:path";
15
+ import { exec } from "node:child_process";
16
+ import { promisify } from "node:util";
17
+ const execAsync = promisify(exec);
18
+ /**
19
+ * The picker list. Must be kept in sync manually with the `TEMPLATES`
20
+ * object in polpo-ui/packages/create-app/bin.mjs. If create-polpo-app
21
+ * adds a new template, users can still pass `--template <newname>` —
22
+ * we just won't list it in the interactive picker until updated here.
23
+ */
24
+ export const TEMPLATES = [
25
+ {
26
+ id: "empty",
27
+ label: "Blank project",
28
+ hint: "just .polpo/ — plug into your existing codebase",
29
+ kind: "blank",
30
+ },
31
+ {
32
+ id: "chat",
33
+ label: "Chat",
34
+ hint: "full-page chat w/ sessions + dark mode (Next.js)",
35
+ kind: "remote",
36
+ installsDeps: true,
37
+ },
38
+ {
39
+ id: "chat-widget",
40
+ label: "Chat widget",
41
+ hint: "embeddable chat widget (Vite/React)",
42
+ kind: "remote",
43
+ installsDeps: true,
44
+ },
45
+ {
46
+ id: "multi-agent",
47
+ label: "Multi-agent",
48
+ hint: "multi-agent workspace (Next.js)",
49
+ kind: "remote",
50
+ installsDeps: true,
51
+ },
52
+ ];
53
+ export function findTemplate(id) {
54
+ return TEMPLATES.find((t) => t.id === id);
55
+ }
56
+ /**
57
+ * Write a minimal .polpo/ scaffold into `targetDir` (blank template).
58
+ * Creates:
59
+ * .polpo/polpo.json — project config (projectId added later by create)
60
+ * .polpo/teams.json — single "default" team
61
+ * .polpo/agents.json — array of wrapped agents: [{agent, teamName}]
62
+ * .env.local.example
63
+ * README.md
64
+ *
65
+ * Layout follows the canonical format read by FileAgentStore / FileTeamStore
66
+ * and validated by `polpo deploy`: agents live in a single agents.json array
67
+ * with each entry as `{ agent: AgentConfig, teamName: string }`.
68
+ */
69
+ export function writeBlankScaffold(targetDir, projectName) {
70
+ fs.mkdirSync(path.join(targetDir, ".polpo"), { recursive: true });
71
+ fs.writeFileSync(path.join(targetDir, ".polpo", "polpo.json"), JSON.stringify({ project: projectName }, null, 2) + "\n");
72
+ fs.writeFileSync(path.join(targetDir, ".polpo", "teams.json"), JSON.stringify([{ name: "default", description: "Default team" }], null, 2) + "\n");
73
+ fs.writeFileSync(path.join(targetDir, ".polpo", "agents.json"), JSON.stringify([
74
+ {
75
+ agent: {
76
+ name: "agent-1",
77
+ role: "helpful assistant",
78
+ model: "xai/grok-4-fast",
79
+ },
80
+ teamName: "default",
81
+ },
82
+ ], null, 2) + "\n");
83
+ fs.writeFileSync(path.join(targetDir, ".env.local.example"), "# Cloud usage: POLPO_API_URL is set automatically by `polpo create` to your\n" +
84
+ "# project's subdomain (https://{slug}.polpo.cloud). Override here only for\n" +
85
+ "# self-hosted, custom domains, or local dev.\n" +
86
+ "POLPO_API_KEY=\n" +
87
+ "POLPO_API_URL=https://your-project-slug.polpo.cloud\n");
88
+ fs.writeFileSync(path.join(targetDir, "README.md"), `# ${projectName}\n\nBuilt with [Polpo](https://polpo.sh).\n\n` +
89
+ "## Commands\n\n" +
90
+ "```bash\n" +
91
+ "polpo deploy # push .polpo/ to cloud\n" +
92
+ "polpo logs # tail cloud logs\n" +
93
+ "```\n");
94
+ }
95
+ /**
96
+ * Shell out to `create-polpo-app` with the right flags to scaffold a
97
+ * remote template into `targetDir`.
98
+ *
99
+ * Uses `-y` (non-interactive) so our wizard owns prompts; `create-polpo-app`
100
+ * runs fully scripted.
101
+ */
102
+ export async function scaffoldRemoteTemplate(opts) {
103
+ const parent = path.dirname(opts.targetDir);
104
+ const name = path.basename(opts.targetDir);
105
+ const flags = [
106
+ name,
107
+ `--template ${opts.templateId}`,
108
+ "-y",
109
+ opts.skipInstall ? "--skip-install" : null,
110
+ ]
111
+ .filter(Boolean)
112
+ .join(" ");
113
+ await execAsync(`npx --yes create-polpo-app@latest ${flags}`, {
114
+ cwd: parent,
115
+ maxBuffer: 10 * 1024 * 1024,
116
+ });
117
+ }
118
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/util/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAelC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAyB;IAC7C;QACE,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,eAAe;QACtB,IAAI,EAAE,iDAAiD;QACvD,IAAI,EAAE,OAAO;KACd;IACD;QACE,EAAE,EAAE,MAAM;QACV,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,kDAAkD;QACxD,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,IAAI;KACnB;IACD;QACE,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,aAAa;QACpB,IAAI,EAAE,qCAAqC;QAC3C,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,IAAI;KACnB;IACD;QACE,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,aAAa;QACpB,IAAI,EAAE,iCAAiC;QACvC,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,IAAI;KACnB;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,WAAmB;IACvE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAElE,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,EAC5C,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CACzD,CAAC;IAEF,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,EAC5C,IAAI,CAAC,SAAS,CACZ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAClD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,CACT,CAAC;IAEF,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,EAC7C,IAAI,CAAC,SAAS,CACZ;QACE;YACE,KAAK,EAAE;gBACL,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,mBAAmB;gBACzB,KAAK,EAAE,iBAAiB;aACzB;YACD,QAAQ,EAAE,SAAS;SACpB;KACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,CACT,CAAC;IAEF,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAC1C,+EAA+E;QAC7E,8EAA8E;QAC9E,gDAAgD;QAChD,kBAAkB;QAClB,uDAAuD,CAC1D,CAAC;IAEF,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,EACjC,KAAK,WAAW,+CAA+C;QAC7D,iBAAiB;QACjB,WAAW;QACX,0CAA0C;QAC1C,oCAAoC;QACpC,OAAO,CACV,CAAC;AACJ,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAA2B;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE3C,MAAM,KAAK,GAAG;QACZ,IAAI;QACJ,cAAc,IAAI,CAAC,UAAU,EAAE;QAC/B,IAAI;QACJ,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI;KAC3C;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,MAAM,SAAS,CAAC,qCAAqC,KAAK,EAAE,EAAE;QAC5D,GAAG,EAAE,MAAM;QACX,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;KAC5B,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@polpo-ai/cli",
3
+ "version": "0.6.0",
4
+ "description": "Command-line client for Polpo — create/link/deploy cloud projects from your terminal.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "bin": {
9
+ "polpo": "dist/index.js",
10
+ "polpo-ai": "dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "dependencies": {
18
+ "@clack/prompts": "^0.9.1",
19
+ "commander": "^13.1.0",
20
+ "picocolors": "^1.1.1",
21
+ "@polpo-ai/server": "0.6.0",
22
+ "@polpo-ai/vault-crypto": "0.6.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.10.2",
26
+ "typescript": "^5.7.3"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "dev": "tsc --watch",
34
+ "typecheck": "tsc --noEmit",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest"
37
+ }
38
+ }