everything-dev 1.3.7 → 1.4.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 (76) hide show
  1. package/dist/cli/init.cjs +34 -5
  2. package/dist/cli/init.cjs.map +1 -1
  3. package/dist/cli/init.d.cts +10 -7
  4. package/dist/cli/init.d.cts.map +1 -1
  5. package/dist/cli/init.d.mts +10 -7
  6. package/dist/cli/init.d.mts.map +1 -1
  7. package/dist/cli/init.mjs +34 -6
  8. package/dist/cli/init.mjs.map +1 -1
  9. package/dist/cli/prompts.cjs +19 -9
  10. package/dist/cli/prompts.cjs.map +1 -1
  11. package/dist/cli/prompts.mjs +19 -9
  12. package/dist/cli/prompts.mjs.map +1 -1
  13. package/dist/cli/snapshot.cjs +35 -0
  14. package/dist/cli/snapshot.cjs.map +1 -0
  15. package/dist/cli/snapshot.mjs +33 -0
  16. package/dist/cli/snapshot.mjs.map +1 -0
  17. package/dist/cli/status.cjs +80 -0
  18. package/dist/cli/status.cjs.map +1 -0
  19. package/dist/cli/status.mjs +79 -0
  20. package/dist/cli/status.mjs.map +1 -0
  21. package/dist/cli/sync.cjs +170 -0
  22. package/dist/cli/sync.cjs.map +1 -0
  23. package/dist/cli/sync.mjs +169 -0
  24. package/dist/cli/sync.mjs.map +1 -0
  25. package/dist/cli/upgrade.cjs +123 -0
  26. package/dist/cli/upgrade.cjs.map +1 -0
  27. package/dist/cli/upgrade.mjs +122 -0
  28. package/dist/cli/upgrade.mjs.map +1 -0
  29. package/dist/cli.cjs +101 -5
  30. package/dist/cli.cjs.map +1 -1
  31. package/dist/cli.mjs +101 -5
  32. package/dist/cli.mjs.map +1 -1
  33. package/dist/contract.cjs +81 -8
  34. package/dist/contract.cjs.map +1 -1
  35. package/dist/contract.d.cts +156 -15
  36. package/dist/contract.d.cts.map +1 -1
  37. package/dist/contract.d.mts +156 -15
  38. package/dist/contract.d.mts.map +1 -1
  39. package/dist/contract.meta.cjs +32 -9
  40. package/dist/contract.meta.cjs.map +1 -1
  41. package/dist/contract.meta.d.cts +50 -11
  42. package/dist/contract.meta.d.mts +50 -11
  43. package/dist/contract.meta.mjs +32 -9
  44. package/dist/contract.meta.mjs.map +1 -1
  45. package/dist/contract.mjs +77 -9
  46. package/dist/contract.mjs.map +1 -1
  47. package/dist/index.cjs +5 -0
  48. package/dist/index.d.cts +2 -2
  49. package/dist/index.d.mts +2 -2
  50. package/dist/index.mjs +2 -2
  51. package/dist/plugin.cjs +123 -43
  52. package/dist/plugin.cjs.map +1 -1
  53. package/dist/plugin.d.cts +75 -8
  54. package/dist/plugin.d.cts.map +1 -1
  55. package/dist/plugin.d.mts +75 -8
  56. package/dist/plugin.d.mts.map +1 -1
  57. package/dist/plugin.mjs +126 -46
  58. package/dist/plugin.mjs.map +1 -1
  59. package/dist/types.d.cts +2 -2
  60. package/dist/types.d.mts +2 -2
  61. package/dist/utils/theme.cjs +1 -0
  62. package/dist/utils/theme.cjs.map +1 -1
  63. package/dist/utils/theme.mjs +1 -0
  64. package/dist/utils/theme.mjs.map +1 -1
  65. package/package.json +1 -1
  66. package/src/cli/init.ts +60 -11
  67. package/src/cli/prompts.ts +34 -16
  68. package/src/cli/snapshot.ts +46 -0
  69. package/src/cli/status.ts +85 -0
  70. package/src/cli/sync.ts +239 -0
  71. package/src/cli/upgrade.ts +165 -0
  72. package/src/cli.ts +152 -5
  73. package/src/contract.meta.ts +36 -6
  74. package/src/contract.ts +74 -7
  75. package/src/plugin.ts +156 -45
  76. package/src/utils/theme.ts +1 -0
@@ -0,0 +1,165 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { UpgradeOptions, UpgradeResult } from "../contract";
4
+ import { runBunInstall } from "./init";
5
+ import { syncTemplate } from "./sync";
6
+
7
+ const FRAMEWORK_PACKAGES = ["everything-dev", "every-plugin"];
8
+
9
+ interface NpmPackageInfo {
10
+ version: string;
11
+ }
12
+
13
+ async function fetchLatestNpmVersion(packageName: string): Promise<string | null> {
14
+ try {
15
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
16
+ headers: { Accept: "application/json" },
17
+ signal: AbortSignal.timeout(10_000),
18
+ });
19
+ if (!response.ok) return null;
20
+ const data = (await response.json()) as NpmPackageInfo;
21
+ return data.version;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function readInstalledVersion(projectDir: string, packageName: string): string | undefined {
28
+ const pkgPath = join(projectDir, "package.json");
29
+ if (!existsSync(pkgPath)) return undefined;
30
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as Record<string, unknown>;
31
+ const deps = (pkg.dependencies ?? {}) as Record<string, string>;
32
+ const devDeps = (pkg.devDependencies ?? {}) as Record<string, string>;
33
+ const version = deps[packageName] || devDeps[packageName];
34
+ if (!version) return undefined;
35
+ return version.replace(/^[\^~>=]+/, "");
36
+ }
37
+
38
+ function updatePackageVersion(projectDir: string, packageName: string, newVersion: string): void {
39
+ const pkgPath = join(projectDir, "package.json");
40
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as Record<string, unknown>;
41
+
42
+ if (pkg.dependencies && typeof pkg.dependencies === "object") {
43
+ const deps = pkg.dependencies as Record<string, string>;
44
+ if (deps[packageName] !== undefined) {
45
+ deps[packageName] = `^${newVersion}`;
46
+ }
47
+ }
48
+
49
+ if (pkg.devDependencies && typeof pkg.devDependencies === "object") {
50
+ const deps = pkg.devDependencies as Record<string, string>;
51
+ if (deps[packageName] !== undefined) {
52
+ deps[packageName] = `^${newVersion}`;
53
+ }
54
+ }
55
+
56
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
57
+ }
58
+
59
+ function buildChangelogUrl(
60
+ oldVersion: string | undefined,
61
+ newVersion: string,
62
+ parentConfig: Record<string, unknown> | null,
63
+ ): string | undefined {
64
+ if (!oldVersion || oldVersion === newVersion) return undefined;
65
+ const repoUrl = parentConfig?.repository as string | undefined;
66
+ if (!repoUrl) return undefined;
67
+
68
+ const githubMatch = repoUrl.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
69
+ if (!githubMatch) return undefined;
70
+
71
+ const [, owner, repo] = githubMatch;
72
+ return `https://github.com/${owner}/${repo}/compare/v${oldVersion}...v${newVersion}`;
73
+ }
74
+
75
+ export async function upgradeTemplate(
76
+ projectDir: string,
77
+ options: UpgradeOptions,
78
+ ): Promise<UpgradeResult> {
79
+ const pkgPath = join(projectDir, "package.json");
80
+ if (!existsSync(pkgPath)) {
81
+ return {
82
+ status: "error",
83
+ packages: [],
84
+ error: "No package.json found in current directory",
85
+ };
86
+ }
87
+
88
+ const packages: UpgradeResult["packages"] = [];
89
+
90
+ for (const name of FRAMEWORK_PACKAGES) {
91
+ const installed = readInstalledVersion(projectDir, name);
92
+ const latest = await fetchLatestNpmVersion(name);
93
+
94
+ if (!latest) {
95
+ packages.push({ name, from: installed, to: installed ?? "unknown" });
96
+ continue;
97
+ }
98
+
99
+ packages.push({ name, from: installed, to: latest });
100
+ }
101
+
102
+ const hasUpdates = packages.some((p) => p.from !== p.to && p.from !== undefined);
103
+
104
+ if (options.dryRun) {
105
+ let changelogUrl: string | undefined;
106
+ if (hasUpdates) {
107
+ const configPath = join(projectDir, "bos.config.json");
108
+ let parentConfig: Record<string, unknown> | null = null;
109
+ if (existsSync(configPath)) {
110
+ try {
111
+ parentConfig = JSON.parse(readFileSync(configPath, "utf-8"));
112
+ } catch {}
113
+ }
114
+ const mainPkg = packages.find((p) => p.name === "everything-dev");
115
+ if (mainPkg?.from && mainPkg.from !== mainPkg.to) {
116
+ changelogUrl = buildChangelogUrl(mainPkg.from, mainPkg.to, parentConfig);
117
+ }
118
+ }
119
+
120
+ return {
121
+ status: "dry-run",
122
+ packages,
123
+ changelogUrl,
124
+ };
125
+ }
126
+
127
+ for (const pkg of packages) {
128
+ if (pkg.from !== undefined && pkg.from !== pkg.to) {
129
+ updatePackageVersion(projectDir, pkg.name, pkg.to);
130
+ }
131
+ }
132
+
133
+ if (hasUpdates && !options.noInstall) {
134
+ await runBunInstall(projectDir);
135
+ }
136
+
137
+ let syncResult: UpgradeResult["sync"];
138
+ if (!options.noSync) {
139
+ syncResult = await syncTemplate(projectDir, {
140
+ dryRun: false,
141
+ force: options.force,
142
+ noInstall: true,
143
+ });
144
+ }
145
+
146
+ let changelogUrl: string | undefined;
147
+ const mainPkg = packages.find((p) => p.name === "everything-dev");
148
+ if (mainPkg?.from && mainPkg.from !== mainPkg.to) {
149
+ const configPath = join(projectDir, "bos.config.json");
150
+ let parentConfig: Record<string, unknown> | null = null;
151
+ if (existsSync(configPath)) {
152
+ try {
153
+ parentConfig = JSON.parse(readFileSync(configPath, "utf-8"));
154
+ } catch {}
155
+ }
156
+ changelogUrl = buildChangelogUrl(mainPkg.from, mainPkg.to, parentConfig);
157
+ }
158
+
159
+ return {
160
+ status: "upgraded",
161
+ packages,
162
+ sync: syncResult,
163
+ changelogUrl,
164
+ };
165
+ }
package/src/cli.ts CHANGED
@@ -32,6 +32,20 @@ function printConfigView(result: {
32
32
  console.log();
33
33
  }
34
34
 
35
+ function formatTimeAgo(isoTimestamp: string): string {
36
+ const now = Date.now();
37
+ const then = new Date(isoTimestamp).getTime();
38
+ const diffMs = now - then;
39
+ const diffMins = Math.floor(diffMs / 60_000);
40
+ if (diffMins < 1) return "just now";
41
+ if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? "s" : ""} ago`;
42
+ const diffHours = Math.floor(diffMins / 60);
43
+ if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? "s" : ""} ago`;
44
+ const diffDays = Math.floor(diffHours / 24);
45
+ if (diffDays < 30) return `${diffDays} day${diffDays > 1 ? "s" : ""} ago`;
46
+ return isoTimestamp.split("T")[0] ?? isoTimestamp;
47
+ }
48
+
35
49
  async function main() {
36
50
  const args = process.argv.slice(2);
37
51
 
@@ -96,18 +110,151 @@ async function main() {
96
110
  }
97
111
  console.log(colors.green(`${icons.ok} Project initialized`));
98
112
  console.log(` ${colors.dim("Extends:")} ${result.extends}`);
99
- console.log(` ${colors.dim("Destination:")} ${result.destination}`);
100
- if (result.name) console.log(` ${colors.dim("Account:")} ${result.name}`);
113
+ console.log(` ${colors.dim("Directory:")} ${result.directory}`);
114
+ if (result.account) console.log(` ${colors.dim("Account:")} ${result.account}`);
101
115
  if (result.domain) console.log(` ${colors.dim("Domain:")} ${result.domain}`);
102
116
  console.log(` ${colors.dim("Files copied:")} ${result.filesCopied}`);
103
117
  console.log();
104
118
  console.log(colors.dim(" Next steps:"));
105
- console.log(colors.dim(` cd ${result.destination}`));
119
+ console.log(colors.dim(` cd ${result.directory}`));
120
+ console.log(colors.dim(" cp .env.example .env # then fill in your values"));
106
121
  if (result.status === "initialized" && !(input as any)?.noInstall) {
107
- console.log(colors.dim(" bos dev --host remote"));
122
+ console.log(colors.dim(" bun run dev"));
108
123
  } else {
109
124
  console.log(colors.dim(" bun install"));
110
- console.log(colors.dim(" bos dev --host remote"));
125
+ console.log(colors.dim(" bun run dev"));
126
+ }
127
+ console.log();
128
+ return;
129
+ }
130
+
131
+ if (descriptor.key === "sync") {
132
+ console.log();
133
+ if (result.status === "error") {
134
+ console.error(`[CLI] ${result.error || "Unknown error"}`);
135
+ process.exit(1);
136
+ }
137
+ if (result.status === "dry-run") {
138
+ console.log(colors.cyan(`${icons.ok} Dry run — no files written`));
139
+ } else {
140
+ console.log(colors.green(`${icons.ok} Template synced`));
141
+ }
142
+ if (result.updated.length > 0) {
143
+ console.log(` ${colors.dim("Updated:")} ${result.updated.length} file(s)`);
144
+ for (const f of result.updated) console.log(` ${colors.dim(f)}`);
145
+ }
146
+ if (result.added.length > 0) {
147
+ console.log(` ${colors.dim("Added:")} ${result.added.length} file(s)`);
148
+ for (const f of result.added) console.log(` ${colors.dim(f)}`);
149
+ }
150
+ if (result.skipped.length > 0) {
151
+ console.log(
152
+ ` ${colors.yellow("Skipped:")} ${result.skipped.length} file(s) (locally modified, use --force to overwrite)`,
153
+ );
154
+ for (const f of result.skipped) console.log(` ${colors.dim(f)}`);
155
+ }
156
+ if (result.updated.length === 0 && result.added.length === 0 && result.skipped.length === 0) {
157
+ console.log(` ${colors.dim("Already up to date")}`);
158
+ }
159
+ console.log();
160
+ return;
161
+ }
162
+
163
+ if (descriptor.key === "upgrade") {
164
+ console.log();
165
+ if (result.status === "error") {
166
+ console.error(`[CLI] ${result.error || "Unknown error"}`);
167
+ process.exit(1);
168
+ }
169
+ if (result.status === "dry-run") {
170
+ console.log(colors.cyan(`${icons.ok} Dry run — no changes applied`));
171
+ } else {
172
+ console.log(colors.green(`${icons.ok} Framework upgraded`));
173
+ }
174
+ for (const pkg of result.packages) {
175
+ if (pkg.from && pkg.from !== pkg.to) {
176
+ console.log(` ${colors.dim(`${pkg.name}:`)} ${pkg.from} → ${pkg.to}`);
177
+ } else if (!pkg.from) {
178
+ console.log(` ${colors.dim(`${pkg.name}:`)} ${pkg.to} (new)`);
179
+ } else {
180
+ console.log(` ${colors.dim(`${pkg.name}:`)} ${pkg.to} (up to date)`);
181
+ }
182
+ }
183
+ if (result.changelogUrl) {
184
+ console.log(` ${colors.dim("Changelog:")} ${result.changelogUrl}`);
185
+ }
186
+ if (result.sync) {
187
+ const sync = result.sync;
188
+ if (sync.updated.length > 0) {
189
+ console.log(` ${colors.dim("Synced updated:")} ${sync.updated.length} file(s)`);
190
+ }
191
+ if (sync.added.length > 0) {
192
+ console.log(` ${colors.dim("Synced added:")} ${sync.added.length} file(s)`);
193
+ }
194
+ if (sync.skipped.length > 0) {
195
+ console.log(
196
+ ` ${colors.yellow("Synced skipped:")} ${sync.skipped.length} file(s) (locally modified, use --force to overwrite)`,
197
+ );
198
+ }
199
+ }
200
+ console.log();
201
+ return;
202
+ }
203
+
204
+ if (descriptor.key === "status") {
205
+ console.log();
206
+ if (result.status === "error") {
207
+ console.error(`[CLI] ${result.error || "Unknown error"}`);
208
+ process.exit(1);
209
+ }
210
+ console.log(colors.cyan(frames.top(52)));
211
+ console.log(` ${icons.app} ${gradients.cyber("STATUS")}`);
212
+ console.log(colors.cyan(frames.bottom(52)));
213
+ console.log();
214
+ if (result.extends) console.log(` ${colors.dim("Extends:")} ${result.extends}`);
215
+ if (result.account) console.log(` ${colors.dim("Account:")} ${result.account}`);
216
+ if (result.domain) console.log(` ${colors.dim("Domain:")} ${result.domain}`);
217
+ console.log();
218
+ console.log(` ${colors.dim("Packages:")}`);
219
+ for (const pkg of result.packages) {
220
+ const hasUpdate = pkg.installed && pkg.latest && pkg.installed !== pkg.latest;
221
+ const versionStr = hasUpdate
222
+ ? `${pkg.installed} → ${pkg.latest}`
223
+ : pkg.installed || "not installed";
224
+ const label = hasUpdate ? colors.yellow(versionStr) : colors.dim(versionStr);
225
+ console.log(` ${colors.dim(`${pkg.name}`)} ${label}`);
226
+ }
227
+ console.log();
228
+ if (result.lastSync) {
229
+ const ago = formatTimeAgo(result.lastSync);
230
+ console.log(` ${colors.dim("Last sync:")} ${ago}`);
231
+ } else {
232
+ console.log(` ${colors.dim("Last sync:")} never`);
233
+ }
234
+ const envLabel =
235
+ result.envFile === "found"
236
+ ? colors.green("found")
237
+ : result.envFile === "example-only"
238
+ ? colors.yellow("missing (only .env.example found)")
239
+ : colors.error("missing");
240
+ console.log(` ${colors.dim(".env:")} ${envLabel}`);
241
+ if (result.parentReachable !== undefined) {
242
+ const parentLabel = result.parentReachable
243
+ ? colors.green("reachable")
244
+ : colors.error("unreachable");
245
+ console.log(` ${colors.dim("Parent:")} ${parentLabel}`);
246
+ }
247
+ const hasUpdates = result.packages.some(
248
+ (p: { installed?: string; latest?: string }) =>
249
+ p.installed && p.latest && p.installed !== p.latest,
250
+ );
251
+ if (hasUpdates) {
252
+ console.log();
253
+ console.log(
254
+ colors.dim(
255
+ ` Run ${colors.cyan("bos upgrade")} to update packages and sync template files.`,
256
+ ),
257
+ );
111
258
  }
112
259
  console.log();
113
260
  return;
@@ -79,18 +79,48 @@ export const cliCommandMeta = {
79
79
  summary: "Scaffold a new project from a bos template",
80
80
  interactive: true,
81
81
  fields: {
82
- account: {
82
+ domain: {
83
83
  positional: true,
84
- description: "Parent NEAR account (defaults to dev.everything.near)",
84
+ description: "New project domain (e.g. ironclaw.everything.dev)",
85
85
  },
86
- gateway: { positional: true, description: "Parent gateway ID (defaults to everything.dev)" },
87
- destination: { description: "Target directory (defaults to ./gateway)" },
88
- name: { description: "New project NEAR account" },
89
- domain: { description: "New project domain" },
86
+ account: { description: "New project NEAR account (auto-derived from domain)" },
87
+ extendsAccount: {
88
+ description: "Parent NEAR account to extend from (defaults to dev.everything.near)",
89
+ },
90
+ extendsGateway: {
91
+ description: "Parent gateway to extend from (defaults to everything.dev)",
92
+ },
93
+ directory: { description: "Target directory (auto-derived from domain)" },
90
94
  source: { description: "Local source dir (skips GitHub download)" },
91
95
  withHost: { description: "Include host/ in template output" },
92
96
  noInteractive: { description: "Skip prompts, use flags only" },
93
97
  noInstall: { description: "Skip bun install" },
94
98
  },
95
99
  },
100
+ sync: {
101
+ commandPath: ["sync"],
102
+ summary: "Sync template files from parent project",
103
+ interactive: false,
104
+ fields: {
105
+ dryRun: { description: "Preview changes without writing files" },
106
+ force: { description: "Overwrite user-modified files" },
107
+ noInstall: { description: "Skip bun install" },
108
+ },
109
+ },
110
+ upgrade: {
111
+ commandPath: ["upgrade"],
112
+ summary: "Upgrade framework packages and sync template files",
113
+ interactive: false,
114
+ fields: {
115
+ dryRun: { description: "Preview changes without writing" },
116
+ force: { description: "Overwrite user-modified files during sync" },
117
+ noInstall: { description: "Skip bun install" },
118
+ noSync: { description: "Only upgrade packages, skip template sync" },
119
+ },
120
+ },
121
+ status: {
122
+ commandPath: ["status"],
123
+ summary: "Show project health, versions, and update availability",
124
+ interactive: false,
125
+ },
96
126
  } as const satisfies Record<string, CliCommandMeta>;
package/src/contract.ts CHANGED
@@ -134,10 +134,10 @@ export const KeyPublishResultSchema = z.object({
134
134
  });
135
135
 
136
136
  export const InitOptionsSchema = z.object({
137
+ extendsAccount: z.string().optional(),
138
+ extendsGateway: z.string().optional(),
139
+ directory: z.string().optional(),
137
140
  account: z.string().optional(),
138
- gateway: z.string().optional(),
139
- destination: z.string().optional(),
140
- name: z.string().optional(),
141
141
  domain: z.string().optional(),
142
142
  source: z.string().optional(),
143
143
  withHost: z.boolean().default(false),
@@ -147,16 +147,69 @@ export const InitOptionsSchema = z.object({
147
147
 
148
148
  export const InitResultSchema = z.object({
149
149
  status: z.enum(["initialized", "error"]),
150
- destination: z.string(),
151
- parentAccount: z.string(),
152
- parentGateway: z.string(),
153
- name: z.string().optional(),
150
+ directory: z.string(),
151
+ extendsAccount: z.string(),
152
+ extendsGateway: z.string(),
153
+ account: z.string().optional(),
154
154
  domain: z.string().optional(),
155
155
  extends: z.string(),
156
156
  filesCopied: z.number(),
157
157
  error: z.string().optional(),
158
158
  });
159
159
 
160
+ export const SyncOptionsSchema = z.object({
161
+ dryRun: z.boolean().default(false),
162
+ force: z.boolean().default(false),
163
+ noInstall: z.boolean().default(false),
164
+ });
165
+
166
+ export const SyncResultSchema = z.object({
167
+ status: z.enum(["synced", "dry-run", "error"]),
168
+ updated: z.array(z.string()),
169
+ skipped: z.array(z.string()),
170
+ added: z.array(z.string()),
171
+ error: z.string().optional(),
172
+ });
173
+
174
+ export const UpgradeOptionsSchema = z.object({
175
+ dryRun: z.boolean().default(false),
176
+ force: z.boolean().default(false),
177
+ noInstall: z.boolean().default(false),
178
+ noSync: z.boolean().default(false),
179
+ });
180
+
181
+ export const UpgradeResultSchema = z.object({
182
+ status: z.enum(["upgraded", "dry-run", "error"]),
183
+ packages: z.array(
184
+ z.object({
185
+ name: z.string(),
186
+ from: z.string().optional(),
187
+ to: z.string(),
188
+ }),
189
+ ),
190
+ sync: SyncResultSchema.optional(),
191
+ changelogUrl: z.string().optional(),
192
+ error: z.string().optional(),
193
+ });
194
+
195
+ export const StatusResultSchema = z.object({
196
+ status: z.enum(["ok", "error"]),
197
+ extends: z.string().optional(),
198
+ account: z.string().optional(),
199
+ domain: z.string().optional(),
200
+ packages: z.array(
201
+ z.object({
202
+ name: z.string(),
203
+ installed: z.string().optional(),
204
+ latest: z.string().optional(),
205
+ }),
206
+ ),
207
+ lastSync: z.string().optional(),
208
+ envFile: z.enum(["found", "missing", "example-only"]),
209
+ parentReachable: z.boolean().optional(),
210
+ error: z.string().optional(),
211
+ });
212
+
160
213
  export const bosContract = oc.router({
161
214
  dev: oc.route({ method: "POST", path: "/dev" }).input(DevOptionsSchema).output(DevResultSchema),
162
215
  start: oc
@@ -193,6 +246,15 @@ export const bosContract = oc.router({
193
246
  .route({ method: "POST", path: "/init" })
194
247
  .input(InitOptionsSchema)
195
248
  .output(InitResultSchema),
249
+ sync: oc
250
+ .route({ method: "POST", path: "/sync" })
251
+ .input(SyncOptionsSchema)
252
+ .output(SyncResultSchema),
253
+ upgrade: oc
254
+ .route({ method: "POST", path: "/upgrade" })
255
+ .input(UpgradeOptionsSchema)
256
+ .output(UpgradeResultSchema),
257
+ status: oc.route({ method: "GET", path: "/status" }).output(StatusResultSchema),
196
258
  });
197
259
 
198
260
  export type DevOptions = z.infer<typeof DevOptionsSchema>;
@@ -211,3 +273,8 @@ export type KeyPublishOptions = z.infer<typeof KeyPublishOptionsSchema>;
211
273
  export type KeyPublishResult = z.infer<typeof KeyPublishResultSchema>;
212
274
  export type InitOptions = z.infer<typeof InitOptionsSchema>;
213
275
  export type InitResult = z.infer<typeof InitResultSchema>;
276
+ export type SyncOptions = z.infer<typeof SyncOptionsSchema>;
277
+ export type SyncResult = z.infer<typeof SyncResultSchema>;
278
+ export type UpgradeOptions = z.infer<typeof UpgradeOptionsSchema>;
279
+ export type UpgradeResult = z.infer<typeof UpgradeResultSchema>;
280
+ export type StatusResult = z.infer<typeof StatusResultSchema>;