opencode-multidevice-sync 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +15 -0
  3. package/README.md +111 -0
  4. package/README.ru.md +57 -0
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -0
  7. package/dist/cli/index.js +460 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/render.d.ts +45 -0
  10. package/dist/cli/render.d.ts.map +1 -0
  11. package/dist/cli/render.js +167 -0
  12. package/dist/cli/render.js.map +1 -0
  13. package/dist/cli/theme.d.ts +45 -0
  14. package/dist/cli/theme.d.ts.map +1 -0
  15. package/dist/cli/theme.js +88 -0
  16. package/dist/cli/theme.js.map +1 -0
  17. package/dist/core/backup.d.ts +13 -0
  18. package/dist/core/backup.d.ts.map +1 -0
  19. package/dist/core/backup.js +102 -0
  20. package/dist/core/backup.js.map +1 -0
  21. package/dist/core/fsx.d.ts +81 -0
  22. package/dist/core/fsx.d.ts.map +1 -0
  23. package/dist/core/fsx.js +353 -0
  24. package/dist/core/fsx.js.map +1 -0
  25. package/dist/core/git.d.ts +62 -0
  26. package/dist/core/git.d.ts.map +1 -0
  27. package/dist/core/git.js +162 -0
  28. package/dist/core/git.js.map +1 -0
  29. package/dist/core/guard.d.ts +5 -0
  30. package/dist/core/guard.d.ts.map +1 -0
  31. package/dist/core/guard.js +97 -0
  32. package/dist/core/guard.js.map +1 -0
  33. package/dist/core/health.d.ts +18 -0
  34. package/dist/core/health.d.ts.map +1 -0
  35. package/dist/core/health.js +88 -0
  36. package/dist/core/health.js.map +1 -0
  37. package/dist/core/host.d.ts +20 -0
  38. package/dist/core/host.d.ts.map +1 -0
  39. package/dist/core/host.js +50 -0
  40. package/dist/core/host.js.map +1 -0
  41. package/dist/core/index.d.ts +18 -0
  42. package/dist/core/index.d.ts.map +1 -0
  43. package/dist/core/index.js +18 -0
  44. package/dist/core/index.js.map +1 -0
  45. package/dist/core/jsonc.d.ts +20 -0
  46. package/dist/core/jsonc.d.ts.map +1 -0
  47. package/dist/core/jsonc.js +115 -0
  48. package/dist/core/jsonc.js.map +1 -0
  49. package/dist/core/lock.d.ts +14 -0
  50. package/dist/core/lock.d.ts.map +1 -0
  51. package/dist/core/lock.js +111 -0
  52. package/dist/core/lock.js.map +1 -0
  53. package/dist/core/overrides.d.ts +43 -0
  54. package/dist/core/overrides.d.ts.map +1 -0
  55. package/dist/core/overrides.js +169 -0
  56. package/dist/core/overrides.js.map +1 -0
  57. package/dist/core/paths.d.ts +36 -0
  58. package/dist/core/paths.d.ts.map +1 -0
  59. package/dist/core/paths.js +55 -0
  60. package/dist/core/paths.js.map +1 -0
  61. package/dist/core/preview.d.ts +3 -0
  62. package/dist/core/preview.d.ts.map +1 -0
  63. package/dist/core/preview.js +56 -0
  64. package/dist/core/preview.js.map +1 -0
  65. package/dist/core/repo.d.ts +57 -0
  66. package/dist/core/repo.d.ts.map +1 -0
  67. package/dist/core/repo.js +196 -0
  68. package/dist/core/repo.js.map +1 -0
  69. package/dist/core/reporter.d.ts +50 -0
  70. package/dist/core/reporter.d.ts.map +1 -0
  71. package/dist/core/reporter.js +63 -0
  72. package/dist/core/reporter.js.map +1 -0
  73. package/dist/core/sessions.d.ts +83 -0
  74. package/dist/core/sessions.d.ts.map +1 -0
  75. package/dist/core/sessions.js +508 -0
  76. package/dist/core/sessions.js.map +1 -0
  77. package/dist/core/settings.d.ts +68 -0
  78. package/dist/core/settings.d.ts.map +1 -0
  79. package/dist/core/settings.js +126 -0
  80. package/dist/core/settings.js.map +1 -0
  81. package/dist/core/setup.d.ts +48 -0
  82. package/dist/core/setup.d.ts.map +1 -0
  83. package/dist/core/setup.js +186 -0
  84. package/dist/core/setup.js.map +1 -0
  85. package/dist/core/sqlite.d.ts +37 -0
  86. package/dist/core/sqlite.d.ts.map +1 -0
  87. package/dist/core/sqlite.js +108 -0
  88. package/dist/core/sqlite.js.map +1 -0
  89. package/dist/core/stage.d.ts +44 -0
  90. package/dist/core/stage.d.ts.map +1 -0
  91. package/dist/core/stage.js +304 -0
  92. package/dist/core/stage.js.map +1 -0
  93. package/dist/core/sync.d.ts +52 -0
  94. package/dist/core/sync.d.ts.map +1 -0
  95. package/dist/core/sync.js +423 -0
  96. package/dist/core/sync.js.map +1 -0
  97. package/dist/plugin/index.d.ts +29 -0
  98. package/dist/plugin/index.d.ts.map +1 -0
  99. package/dist/plugin/index.js +88 -0
  100. package/dist/plugin/index.js.map +1 -0
  101. package/docs/MIGRATION.md +29 -0
  102. package/docs/ROADMAP.md +29 -0
  103. package/docs/SAFETY.md +31 -0
  104. package/examples/home-laptop.jsonc +31 -0
  105. package/examples/opencode-sync.overrides.jsonc +4 -0
  106. package/examples/work-laptop.jsonc +26 -0
  107. package/package.json +81 -0
@@ -0,0 +1,126 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parseJsonc } from "./jsonc.js";
4
+ import { getConfigRoot } from "./paths.js";
5
+ /** Filename of the tool's own settings (local-only, never committed). */
6
+ export const SETTINGS_FILE = "opencode-sync.jsonc";
7
+ /** Filename of the per-machine OpenCode config patch (local-only). */
8
+ export const OVERRIDES_FILE = "opencode-sync.overrides.jsonc";
9
+ export const DEFAULT_SETTINGS = {
10
+ repo: { branch: "main" },
11
+ includeCredentials: false,
12
+ includeSkills: true,
13
+ includeState: false,
14
+ extraPaths: [],
15
+ sessions: {
16
+ enabled: false,
17
+ days: 7,
18
+ maxSessions: 50,
19
+ maxSessionBytes: 5 * 1024 * 1024,
20
+ include: [],
21
+ exclude: [],
22
+ directories: [],
23
+ backupBeforeImport: true,
24
+ backupRetention: 10,
25
+ pathMappings: [],
26
+ },
27
+ autoPullOnStartup: true,
28
+ autoPushOnIdle: false,
29
+ };
30
+ export function settingsPath(configRoot = getConfigRoot()) {
31
+ return path.join(configRoot, SETTINGS_FILE);
32
+ }
33
+ export function overridesPath(configRoot = getConfigRoot()) {
34
+ return path.join(configRoot, OVERRIDES_FILE);
35
+ }
36
+ function mergeSettings(base, patch) {
37
+ return {
38
+ ...base,
39
+ ...patch,
40
+ repo: { ...base.repo, ...(patch.repo ?? {}) },
41
+ sessions: { ...base.sessions, ...(patch.sessions ?? {}) },
42
+ };
43
+ }
44
+ export function loadSettings(configRoot = getConfigRoot()) {
45
+ const file = settingsPath(configRoot);
46
+ if (!fs.existsSync(file))
47
+ return { ...DEFAULT_SETTINGS };
48
+ let parsed;
49
+ try {
50
+ parsed = parseJsonc(fs.readFileSync(file, "utf8"));
51
+ }
52
+ catch (e) {
53
+ throw new Error(`Cannot read ${SETTINGS_FILE}: ${e.message}`);
54
+ }
55
+ return mergeSettings(DEFAULT_SETTINGS, parsed ?? {});
56
+ }
57
+ export function saveSettings(settings, configRoot = getConfigRoot()) {
58
+ fs.mkdirSync(configRoot, { recursive: true });
59
+ fs.writeFileSync(settingsPath(configRoot), `${JSON.stringify(settings, null, 2)}\n`);
60
+ }
61
+ export function repoUrl(settings) {
62
+ if (process.env.SYNC_REMOTE_URL)
63
+ return process.env.SYNC_REMOTE_URL;
64
+ if (settings.repo.url)
65
+ return settings.repo.url;
66
+ if (settings.repo.owner && settings.repo.name) {
67
+ return `https://github.com/${settings.repo.owner}/${settings.repo.name}.git`;
68
+ }
69
+ return undefined;
70
+ }
71
+ export function repoBranch(settings) {
72
+ return settings.repo.branch || "main";
73
+ }
74
+ export const SETTINGS_TEMPLATE = `{
75
+ // opencode-multidevice-sync settings. This file is never committed.
76
+ "repo": {
77
+ "owner": "YOUR_GITHUB_USERNAME",
78
+ "name": "my-opencode-config",
79
+ "branch": "main"
80
+ },
81
+
82
+ // A short name for this machine, used in commit messages.
83
+ // Leave unset to auto-generate a stable pseudonym from the hostname.
84
+ // "machineAlias": "laptop",
85
+
86
+ // Sync auth.json / account.json. Only enable for a PRIVATE repository.
87
+ "includeCredentials": false,
88
+
89
+ "includeSkills": true,
90
+ "includeState": false,
91
+
92
+ // Selective session sync. Off by default.
93
+ "sessions": {
94
+ "enabled": false,
95
+ "days": 7,
96
+ "maxSessions": 50,
97
+ "maxSessionBytes": 5242880,
98
+ "include": [],
99
+ "exclude": [],
100
+ "directories": [],
101
+ "backupBeforeImport": true,
102
+ "backupRetention": 10,
103
+ "pathMappings": []
104
+ },
105
+
106
+ "autoPullOnStartup": true,
107
+ "autoPushOnIdle": false
108
+ }
109
+ `;
110
+ export const OVERRIDES_TEMPLATE = `{
111
+ // Per-machine OpenCode configuration.
112
+ //
113
+ // This file is never committed. After every pull its contents are merged
114
+ // back into opencode.json(c), so machine-specific settings survive syncing.
115
+ //
116
+ // Merge rules:
117
+ // - objects merge key by key
118
+ // - arrays and scalars replace whatever is in the shared config
119
+ // - null deletes the key
120
+ //
121
+ // Example:
122
+ // "model": "github-copilot/claude-sonnet-4",
123
+ // "mcp": { "playwright": { "enabled": true } }
124
+ }
125
+ `;
126
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/core/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { type Roots } from "./paths.js";
2
+ import type { Reporter } from "./reporter.js";
3
+ /**
4
+ * First-run setup.
5
+ *
6
+ * The single biggest barrier to a sync tool is the setup: create a repository,
7
+ * find its URL, put it somewhere the tool can read, authenticate. All of that
8
+ * is automated through the GitHub CLI, which most OpenCode users already have
9
+ * authenticated. Every step is idempotent, so re-running after a failure is
10
+ * always safe.
11
+ */
12
+ export declare class SetupError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ export declare function ghAvailable(): boolean;
16
+ export declare function assertGhReady(): void;
17
+ export declare function currentGitHubUser(): string;
18
+ export interface RepoTarget {
19
+ owner: string;
20
+ name: string;
21
+ }
22
+ /** Accept `name`, `owner/name`, or nothing (defaults applied by the caller). */
23
+ export declare function parseRepoTarget(input: string | undefined, defaultOwner: string): RepoTarget;
24
+ export declare function repoExists(target: RepoTarget): boolean;
25
+ export declare function repoIsPrivate(target: RepoTarget): boolean;
26
+ export declare function createRepo(target: RepoTarget, reporter: Reporter): void;
27
+ export interface SetupOptions {
28
+ repo?: string;
29
+ reporter: Reporter;
30
+ roots?: Roots;
31
+ /** Enable credential syncing. Only allowed for private repositories. */
32
+ includeCredentials?: boolean;
33
+ /** Enable selective session syncing. */
34
+ includeSessions?: boolean;
35
+ }
36
+ export interface SetupResult {
37
+ target: RepoTarget;
38
+ created: boolean;
39
+ settingsFile: string;
40
+ overridesFile: string;
41
+ }
42
+ /** Create (or adopt) a repository and write the local settings files. */
43
+ export declare function initSync(options: SetupOptions): SetupResult;
44
+ /** Point this machine at an existing sync repository. */
45
+ export declare function linkSync(options: SetupOptions): SetupResult;
46
+ /** Guess which of the user's repositories is a sync repository. */
47
+ export declare function discoverSyncRepos(): string[];
48
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/core/setup.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,KAAK,EAAY,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAY9C;;;;;;;;GAQG;AAEH,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAeD,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAUpC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAM1C;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,UAAU,CAS3F;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAEtD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAWzD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAavE;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wCAAwC;IACxC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW,CAe3D;AAED,yDAAyD;AACzD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW,CAY3D;AAqED,mEAAmE;AACnE,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAe5C"}
@@ -0,0 +1,186 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import { ensureFile } from "./fsx.js";
4
+ import { getRoots } from "./paths.js";
5
+ import { DEFAULT_SETTINGS, OVERRIDES_TEMPLATE, SETTINGS_TEMPLATE, loadSettings, overridesPath, saveSettings, settingsPath, } from "./settings.js";
6
+ /**
7
+ * First-run setup.
8
+ *
9
+ * The single biggest barrier to a sync tool is the setup: create a repository,
10
+ * find its URL, put it somewhere the tool can read, authenticate. All of that
11
+ * is automated through the GitHub CLI, which most OpenCode users already have
12
+ * authenticated. Every step is idempotent, so re-running after a failure is
13
+ * always safe.
14
+ */
15
+ export class SetupError extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "SetupError";
19
+ }
20
+ }
21
+ function gh(args) {
22
+ try {
23
+ const stdout = execFileSync("gh", args, { encoding: "utf8", stdio: "pipe" });
24
+ return { ok: true, stdout: stdout.trim(), stderr: "" };
25
+ }
26
+ catch (e) {
27
+ return {
28
+ ok: false,
29
+ stdout: String(e.stdout ?? "").trim(),
30
+ stderr: String(e.stderr ?? e.message ?? "").trim(),
31
+ };
32
+ }
33
+ }
34
+ export function ghAvailable() {
35
+ return gh(["--version"]).ok;
36
+ }
37
+ export function assertGhReady() {
38
+ if (!ghAvailable()) {
39
+ throw new SetupError("The GitHub CLI (`gh`) is required for automatic setup.\n" +
40
+ "Install it from https://cli.github.com, or configure the repository by hand in opencode-sync.jsonc.");
41
+ }
42
+ if (!gh(["auth", "status"]).ok) {
43
+ throw new SetupError("You are not signed in to GitHub. Run: gh auth login");
44
+ }
45
+ }
46
+ export function currentGitHubUser() {
47
+ const result = gh(["api", "user", "--jq", ".login"]);
48
+ if (!result.ok || !result.stdout) {
49
+ throw new SetupError("Could not determine your GitHub username. Run: gh auth login");
50
+ }
51
+ return result.stdout;
52
+ }
53
+ /** Accept `name`, `owner/name`, or nothing (defaults applied by the caller). */
54
+ export function parseRepoTarget(input, defaultOwner) {
55
+ const value = (input ?? "").trim();
56
+ if (!value)
57
+ return { owner: defaultOwner, name: "my-opencode-config" };
58
+ if (value.includes("/")) {
59
+ const [owner, name] = value.split("/", 2);
60
+ if (!owner || !name)
61
+ throw new SetupError(`Not a valid repository: ${value}`);
62
+ return { owner, name };
63
+ }
64
+ return { owner: defaultOwner, name: value };
65
+ }
66
+ export function repoExists(target) {
67
+ return gh(["repo", "view", `${target.owner}/${target.name}`, "--json", "name"]).ok;
68
+ }
69
+ export function repoIsPrivate(target) {
70
+ const result = gh([
71
+ "repo",
72
+ "view",
73
+ `${target.owner}/${target.name}`,
74
+ "--json",
75
+ "visibility",
76
+ "--jq",
77
+ ".visibility",
78
+ ]);
79
+ return result.ok && result.stdout.toUpperCase() === "PRIVATE";
80
+ }
81
+ export function createRepo(target, reporter) {
82
+ reporter.step(`Creating private repository ${target.owner}/${target.name}`);
83
+ const result = gh([
84
+ "repo",
85
+ "create",
86
+ `${target.owner}/${target.name}`,
87
+ "--private",
88
+ "--description",
89
+ "OpenCode configuration synced by opencode-multidevice-sync",
90
+ ]);
91
+ if (!result.ok) {
92
+ throw new SetupError(`Could not create the repository: ${result.stderr || result.stdout}`);
93
+ }
94
+ }
95
+ /** Create (or adopt) a repository and write the local settings files. */
96
+ export function initSync(options) {
97
+ assertGhReady();
98
+ const roots = options.roots ?? getRoots();
99
+ const owner = currentGitHubUser();
100
+ const target = parseRepoTarget(options.repo, owner);
101
+ let created = false;
102
+ if (!repoExists(target)) {
103
+ createRepo(target, options.reporter);
104
+ created = true;
105
+ }
106
+ else {
107
+ options.reporter.info(`Using the existing repository ${target.owner}/${target.name}`);
108
+ }
109
+ return writeSetupFiles(target, options, roots, created);
110
+ }
111
+ /** Point this machine at an existing sync repository. */
112
+ export function linkSync(options) {
113
+ assertGhReady();
114
+ const roots = options.roots ?? getRoots();
115
+ const owner = currentGitHubUser();
116
+ const target = parseRepoTarget(options.repo, owner);
117
+ if (!repoExists(target)) {
118
+ throw new SetupError(`Repository ${target.owner}/${target.name} was not found, or your account cannot see it.`);
119
+ }
120
+ return writeSetupFiles(target, options, roots, false);
121
+ }
122
+ function writeSetupFiles(target, options, roots, created) {
123
+ const isPrivate = repoIsPrivate(target);
124
+ if (options.includeCredentials && !isPrivate) {
125
+ throw new SetupError("Credential syncing was requested but the repository is public.\n" +
126
+ "Make it private first, or leave `includeCredentials` off.");
127
+ }
128
+ if (options.includeSessions && !isPrivate) {
129
+ throw new SetupError("Session syncing was requested but the repository is public.\n" +
130
+ "Conversations are private data; make the repository private first.");
131
+ }
132
+ fs.mkdirSync(roots.config, { recursive: true });
133
+ const existing = fs.existsSync(settingsPath(roots.config))
134
+ ? loadSettings(roots.config)
135
+ : { ...DEFAULT_SETTINGS };
136
+ const settings = {
137
+ ...existing,
138
+ repo: {
139
+ ...existing.repo,
140
+ owner: target.owner,
141
+ name: target.name,
142
+ branch: existing.repo.branch || "main",
143
+ },
144
+ includeCredentials: options.includeCredentials ?? existing.includeCredentials,
145
+ sessions: {
146
+ ...existing.sessions,
147
+ enabled: options.includeSessions ?? existing.sessions.enabled,
148
+ },
149
+ };
150
+ if (fs.existsSync(settingsPath(roots.config))) {
151
+ saveSettings(settings, roots.config);
152
+ }
153
+ else {
154
+ // A first-time file keeps the annotated template so the user can see every
155
+ // available option instead of a bare JSON dump.
156
+ fs.writeFileSync(settingsPath(roots.config), SETTINGS_TEMPLATE.replace("YOUR_GITHUB_USERNAME", target.owner).replace('"name": "my-opencode-config"', `"name": "${target.name}"`));
157
+ if (options.includeCredentials || options.includeSessions)
158
+ saveSettings(settings, roots.config);
159
+ }
160
+ ensureFile(overridesPath(roots.config), OVERRIDES_TEMPLATE);
161
+ return {
162
+ target,
163
+ created,
164
+ settingsFile: settingsPath(roots.config),
165
+ overridesFile: overridesPath(roots.config),
166
+ };
167
+ }
168
+ /** Guess which of the user's repositories is a sync repository. */
169
+ export function discoverSyncRepos() {
170
+ const result = gh([
171
+ "repo",
172
+ "list",
173
+ "--limit",
174
+ "200",
175
+ "--json",
176
+ "name,owner,visibility",
177
+ "--jq",
178
+ '.[] | select(.visibility == "PRIVATE") | "\\(.owner.login)/\\(.name)"',
179
+ ]);
180
+ if (!result.ok)
181
+ return [];
182
+ const candidates = result.stdout.split("\n").filter(Boolean);
183
+ const preferred = /opencode|my-opencode-config|dotfiles|config/i;
184
+ return candidates.filter((name) => preferred.test(name));
185
+ }
186
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/core/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAc,QAAQ,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EAEjB,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,YAAY,GACb,MAAM,eAAe,CAAC;AAEvB;;;;;;;;GAQG;AAEH,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,SAAS,EAAE,CAAC,IAAc;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACzD,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;YACrC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;SACnD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,UAAU,CAClB,0DAA0D;YACxD,qGAAqG,CACxG,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,IAAI,UAAU,CAAC,qDAAqD,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,IAAI,UAAU,CAAC,8DAA8D,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAOD,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,KAAyB,EAAE,YAAoB;IAC7E,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC;IACvE,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,UAAU,CAAC,2BAA2B,KAAK,EAAE,CAAC,CAAC;QAC9E,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAkB;IAC9C,MAAM,MAAM,GAAG,EAAE,CAAC;QAChB,MAAM;QACN,MAAM;QACN,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE;QAChC,QAAQ;QACR,YAAY;QACZ,MAAM;QACN,aAAa;KACd,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAkB,EAAE,QAAkB;IAC/D,QAAQ,CAAC,IAAI,CAAC,+BAA+B,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,EAAE,CAAC;QAChB,MAAM;QACN,QAAQ;QACR,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE;QAChC,WAAW;QACX,eAAe;QACf,4DAA4D;KAC7D,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,IAAI,UAAU,CAAC,oCAAoC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7F,CAAC;AACH,CAAC;AAmBD,yEAAyE;AACzE,MAAM,UAAU,QAAQ,CAAC,OAAqB;IAC5C,aAAa,EAAE,CAAC;IAChB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,QAAQ,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAEpD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,iCAAiC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAC1D,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,QAAQ,CAAC,OAAqB;IAC5C,aAAa,EAAE,CAAC;IAChB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,QAAQ,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAEpD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAClB,cAAc,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,gDAAgD,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,eAAe,CACtB,MAAkB,EAClB,OAAqB,EACrB,KAAY,EACZ,OAAgB;IAEhB,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAExC,IAAI,OAAO,CAAC,kBAAkB,IAAI,CAAC,SAAS,EAAE,CAAC;QAC7C,MAAM,IAAI,UAAU,CAClB,kEAAkE;YAChE,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,eAAe,IAAI,CAAC,SAAS,EAAE,CAAC;QAC1C,MAAM,IAAI,UAAU,CAClB,+DAA+D;YAC7D,oEAAoE,CACvE,CAAC;IACJ,CAAC;IAED,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEhD,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACxD,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC;QAC5B,CAAC,CAAC,EAAE,GAAG,gBAAgB,EAAE,CAAC;IAE5B,MAAM,QAAQ,GAAa;QACzB,GAAG,QAAQ;QACX,IAAI,EAAE;YACJ,GAAG,QAAQ,CAAC,IAAI;YAChB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM;SACvC;QACD,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,IAAI,QAAQ,CAAC,kBAAkB;QAC7E,QAAQ,EAAE;YACR,GAAG,QAAQ,CAAC,QAAQ;YACpB,OAAO,EAAE,OAAO,CAAC,eAAe,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO;SAC9D;KACF,CAAC;IAEF,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAC9C,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,2EAA2E;QAC3E,gDAAgD;QAChD,EAAE,CAAC,aAAa,CACd,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,EAC1B,iBAAiB,CAAC,OAAO,CAAC,sBAAsB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CACrE,8BAA8B,EAC9B,YAAY,MAAM,CAAC,IAAI,GAAG,CAC3B,CACF,CAAC;QACF,IAAI,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAC,eAAe;YAAE,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClG,CAAC;IAED,UAAU,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAE5D,OAAO;QACL,MAAM;QACN,OAAO;QACP,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC;QACxC,aAAa,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC;KAC3C,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAG,EAAE,CAAC;QAChB,MAAM;QACN,MAAM;QACN,SAAS;QACT,KAAK;QACL,QAAQ;QACR,uBAAuB;QACvB,MAAM;QACN,uEAAuE;KACxE,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,8CAA8C,CAAC;IACjE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Minimal SQLite adapter.
3
+ *
4
+ * OpenCode stores every session in a single SQLite database. We need to read
5
+ * and write it without dragging in a native dependency, because this package is
6
+ * loaded inside the OpenCode runtime where a compiled addon would have to match
7
+ * whatever engine the user happens to be running.
8
+ *
9
+ * Two built-in drivers are supported, in preference order:
10
+ * 1. `bun:sqlite` — present when OpenCode runs the plugin under Bun
11
+ * 2. `node:sqlite` — Node 22.5+
12
+ *
13
+ * Neither requires an install step. When neither is available session sync is
14
+ * disabled with a clear message rather than failing halfway through.
15
+ */
16
+ export interface SqliteStatement {
17
+ all(...params: unknown[]): any[];
18
+ run(...params: unknown[]): unknown;
19
+ }
20
+ export interface SqliteDatabase {
21
+ prepare(sql: string): SqliteStatement;
22
+ exec(sql: string): void;
23
+ close(): void;
24
+ }
25
+ export interface OpenOptions {
26
+ readOnly?: boolean;
27
+ }
28
+ export declare class SqliteUnavailableError extends Error {
29
+ constructor();
30
+ }
31
+ export declare function sqliteAvailable(): Promise<boolean>;
32
+ export declare function openDatabase(file: string, options?: OpenOptions): Promise<SqliteDatabase>;
33
+ /** Which of the given tables actually exist in this database. */
34
+ export declare function existingTables(db: SqliteDatabase, candidates: string[]): string[];
35
+ /** Column names of a table, in declaration order. */
36
+ export declare function tableColumns(db: SqliteDatabase, table: string): string[];
37
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/core/sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,EAAE,CAAC;IACjC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsCD,qBAAa,sBAAuB,SAAQ,KAAK;;CAQhD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAExD;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,cAAc,CAAC,CAsCzB;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAMjF;AAED,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAGxE"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Minimal SQLite adapter.
3
+ *
4
+ * OpenCode stores every session in a single SQLite database. We need to read
5
+ * and write it without dragging in a native dependency, because this package is
6
+ * loaded inside the OpenCode runtime where a compiled addon would have to match
7
+ * whatever engine the user happens to be running.
8
+ *
9
+ * Two built-in drivers are supported, in preference order:
10
+ * 1. `bun:sqlite` — present when OpenCode runs the plugin under Bun
11
+ * 2. `node:sqlite` — Node 22.5+
12
+ *
13
+ * Neither requires an install step. When neither is available session sync is
14
+ * disabled with a clear message rather than failing halfway through.
15
+ */
16
+ /**
17
+ * Built from parts so bundlers and TypeScript never try to resolve the Bun
18
+ * built-in at build time. It only ever exists inside the Bun runtime.
19
+ */
20
+ const BUN_SQLITE = ["bun", "sqlite"].join(":");
21
+ async function importBunSqlite() {
22
+ return import(/* @vite-ignore */ /* webpackIgnore: true */ BUN_SQLITE);
23
+ }
24
+ let cachedDriver;
25
+ async function detectDriver() {
26
+ if (cachedDriver)
27
+ return cachedDriver;
28
+ if (typeof globalThis.Bun !== "undefined") {
29
+ try {
30
+ await importBunSqlite();
31
+ cachedDriver = "bun";
32
+ return cachedDriver;
33
+ }
34
+ catch {
35
+ // Fall through to the Node driver.
36
+ }
37
+ }
38
+ try {
39
+ const mod = await import("node:sqlite");
40
+ if (mod?.DatabaseSync) {
41
+ cachedDriver = "node";
42
+ return cachedDriver;
43
+ }
44
+ }
45
+ catch {
46
+ // Not available on this runtime.
47
+ }
48
+ cachedDriver = "none";
49
+ return cachedDriver;
50
+ }
51
+ export class SqliteUnavailableError extends Error {
52
+ constructor() {
53
+ super("Session sync needs SQLite support. Run OpenCode under Bun, or use Node 22.5 or newer.\n" +
54
+ "Configuration sync works without it — only `sessions` is affected.");
55
+ this.name = "SqliteUnavailableError";
56
+ }
57
+ }
58
+ export async function sqliteAvailable() {
59
+ return (await detectDriver()) !== "none";
60
+ }
61
+ export async function openDatabase(file, options = {}) {
62
+ const driver = await detectDriver();
63
+ if (driver === "bun") {
64
+ const { Database } = await importBunSqlite();
65
+ const db = new Database(file, options.readOnly ? { readonly: true } : { create: true });
66
+ db.exec("PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;");
67
+ return {
68
+ prepare: (sql) => {
69
+ const stmt = db.prepare(sql);
70
+ return {
71
+ all: (...params) => stmt.all(...params),
72
+ run: (...params) => stmt.run(...params),
73
+ };
74
+ },
75
+ exec: (sql) => db.exec(sql),
76
+ close: () => db.close(),
77
+ };
78
+ }
79
+ if (driver === "node") {
80
+ const { DatabaseSync } = (await import("node:sqlite"));
81
+ const db = new DatabaseSync(file, options.readOnly ? { readOnly: true } : {});
82
+ db.exec("PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;");
83
+ return {
84
+ prepare: (sql) => {
85
+ const stmt = db.prepare(sql);
86
+ return {
87
+ all: (...params) => stmt.all(...params),
88
+ run: (...params) => stmt.run(...params),
89
+ };
90
+ },
91
+ exec: (sql) => db.exec(sql),
92
+ close: () => db.close(),
93
+ };
94
+ }
95
+ throw new SqliteUnavailableError();
96
+ }
97
+ /** Which of the given tables actually exist in this database. */
98
+ export function existingTables(db, candidates) {
99
+ const rows = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all();
100
+ const present = new Set(rows.map((r) => r.name));
101
+ return candidates.filter((name) => present.has(name));
102
+ }
103
+ /** Column names of a table, in declaration order. */
104
+ export function tableColumns(db, table) {
105
+ const rows = db.prepare(`PRAGMA table_info("${table}")`).all();
106
+ return rows.map((r) => r.name);
107
+ }
108
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/core/sqlite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { type Roots } from "./paths.js";
2
+ import type { Reporter } from "./reporter.js";
3
+ import type { Settings } from "./settings.js";
4
+ export interface StageContext {
5
+ repoRoot: string;
6
+ roots: Roots;
7
+ settings: Settings;
8
+ reporter: Reporter;
9
+ force: boolean;
10
+ }
11
+ export declare function createStageContext(repoRoot: string, settings: Settings, reporter: Reporter, force: boolean, roots?: Roots): StageContext;
12
+ /** Copy everything from the machine into the repository worktree. */
13
+ export declare function stageIn(ctx: StageContext): void;
14
+ /**
15
+ * Rewrite the repository's config file with override-owned keys removed.
16
+ *
17
+ * The baseline comes from `git show HEAD:<file>`, not from disk: the worktree
18
+ * and the config directory are the same place, so the file on disk already has
19
+ * this machine's overrides applied to it.
20
+ *
21
+ * Without overrides nothing happens at all, which keeps comments and formatting
22
+ * byte-identical for the majority of users.
23
+ *
24
+ * Returns true when the file on disk was changed.
25
+ */
26
+ export declare function normalizeConfigForRepo(ctx: StageContext): boolean;
27
+ /** Copy everything from the repository worktree back onto the machine. */
28
+ export declare function stageOut(ctx: StageContext): void;
29
+ /**
30
+ * Drop cached plugin packages that the config no longer references.
31
+ *
32
+ * Only `package.json` and the stale module directories are touched; OpenCode
33
+ * reinstalls whatever it still needs on the next start. Returns how many
34
+ * packages were removed.
35
+ */
36
+ export declare function prunePluginCache(configRoot: string, cacheRoot: string): number;
37
+ /**
38
+ * Reduce a plugin specifier to its package name.
39
+ *
40
+ * `opencode-foo@1.2.3` -> `opencode-foo`
41
+ * `@scope/opencode-foo@1.2.3` -> `@scope/opencode-foo`
42
+ */
43
+ export declare function pluginSpecToName(spec: string): string;
44
+ //# sourceMappingURL=stage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../../src/core/stage.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAwB,KAAK,KAAK,EAAuB,MAAM,YAAY,CAAC;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAkC9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,OAAO,EACd,KAAK,GAAE,KAAkB,GACxB,YAAY,CAEd;AAMD,qEAAqE;AACrE,wBAAgB,OAAO,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CA8C/C;AAyCD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAUjE;AAsCD,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CA0ChD;AAsBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA+B9E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYrD"}