sideby 0.11.0 → 0.12.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.
- package/README.md +2 -2
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +7 -0
- package/dist/src/cli/commands.js +94 -2
- package/dist/src/cli/json-schemas.js +25 -0
- package/dist/src/cli/main.js +104 -43
- package/dist/src/core/brand-mark.d.ts +75 -21
- package/dist/src/core/brand-mark.js +80 -34
- package/dist/src/core/config.d.ts +16 -0
- package/dist/src/core/config.js +17 -2
- package/dist/src/core/update.d.ts +209 -0
- package/dist/src/core/update.js +631 -0
- package/dist/src/desktop/icon.js +292 -61
- package/dist/src/panel/background.d.ts +2 -0
- package/dist/src/panel/background.js +7 -0
- package/dist/src/panel/handler.d.ts +32 -0
- package/dist/src/panel/handler.js +95 -5
- package/dist/src/panel/i18n.d.ts +29 -0
- package/dist/src/panel/i18n.js +58 -0
- package/dist/src/panel/page-script.js +133 -1
- package/dist/src/panel/page-styles.js +6 -2
- package/dist/src/panel/page.d.ts +2 -0
- package/dist/src/panel/page.js +4 -2
- package/dist/testing/index.js +2 -0
- package/package.json +2 -2
- package/schemas/config.json +15 -0
- package/schemas/update.json +115 -0
- package/skills/sideby/SKILL.md +5 -1
package/dist/src/core/config.js
CHANGED
|
@@ -93,6 +93,14 @@ export const ConfigSchema = Type.Object({
|
|
|
93
93
|
description: 'Make `sideby shell-init` also define Host-named functions (`claude`, `codex`, `grok`) that resume a session by id in the Account that holds it. Off by default.',
|
|
94
94
|
})),
|
|
95
95
|
handoff: Type.Optional(HandoffSchema),
|
|
96
|
+
update: Type.Optional(Type.Object({
|
|
97
|
+
check: Type.Optional(Type.Boolean({
|
|
98
|
+
description: 'Ask the npm registry once a day whether a newer sideby exists, and say so after a command and in the Panel. On by default; `NO_UPDATE_NOTIFIER` or `SIDEBY_NO_UPDATE_NOTIFIER` also turn it off.',
|
|
99
|
+
})),
|
|
100
|
+
auto: Type.Optional(Type.Boolean({
|
|
101
|
+
description: 'Install a newer version by itself after a check, when no `sideby run` is running and the version is not a major one. Off by default.',
|
|
102
|
+
})),
|
|
103
|
+
}, { additionalProperties: false, description: 'Update Check and Auto Update (ADR-0021).' })),
|
|
96
104
|
shellInitFile: Type.Optional(Type.Object({
|
|
97
105
|
zsh: Type.Optional(Type.String({ minLength: 1 })),
|
|
98
106
|
bash: Type.Optional(Type.String({ minLength: 1 })),
|
|
@@ -158,8 +166,8 @@ async function configTarget(file) {
|
|
|
158
166
|
return (await lstatOrNull(file))?.isSymbolicLink() ? ((await realpathOrNull(file)) ?? file) : file;
|
|
159
167
|
}
|
|
160
168
|
/**
|
|
161
|
-
* Re-reads the config file, lets `edit` change one top-level `key` (
|
|
162
|
-
* `
|
|
169
|
+
* Re-reads the config file, lets `edit` change one top-level `key` (sideby writes only `aliases`, `handoff`,
|
|
170
|
+
* `accounts` and `update`, spec §3.10), and writes the result: every other key and their order kept (with `$schema`), 2-space JSON
|
|
163
171
|
* with a trailing newline, atomically, keeping the file's mode (0600 for a new file). Refuses, without writing, a
|
|
164
172
|
* file that is not valid JSON or not a valid config, a result that is not a valid config, or a file that changed
|
|
165
173
|
* while sideby wrote it. `edit` returns null to leave the file alone; `dryRun` returns the change without writing.
|
|
@@ -224,6 +232,13 @@ async function editConfigAliases(file, retry, edit) {
|
|
|
224
232
|
export function editConfigHandoff(file, edit, opts = {}) {
|
|
225
233
|
return editConfigKey(file, 'handoff', 'run the command', (current) => edit(current ?? {}), opts);
|
|
226
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* The Panel's Auto Update switch (spec §3.23): changes the config `update` key with `edit` (see `editConfigKey` for
|
|
237
|
+
* how the file is written). With `dryRun`, shows the change without writing.
|
|
238
|
+
*/
|
|
239
|
+
export function editConfigUpdate(file, edit, opts = {}) {
|
|
240
|
+
return editConfigKey(file, 'update', 'change the setting', (current) => edit(current ?? {}), opts);
|
|
241
|
+
}
|
|
227
242
|
/**
|
|
228
243
|
* `sideby model` (spec §3.19): changes the config `accounts` key with `edit` (see `editConfigKey` for how the file is
|
|
229
244
|
* written). With `dryRun`, shows the change without writing.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { Env } from '../types.ts';
|
|
2
|
+
import type { Paths } from './paths.ts';
|
|
3
|
+
export declare const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
4
|
+
export declare const CHECK_INTERVAL_MS: number;
|
|
5
|
+
export declare const NOTICE_INTERVAL_MS: number;
|
|
6
|
+
/** After a failed check (offline, a mirror without the endpoint) the next one waits this long. */
|
|
7
|
+
export declare const RETRY_INTERVAL_MS: number;
|
|
8
|
+
/** A failed Auto Update of one version is tried again after this long (a mirror may not have it yet). */
|
|
9
|
+
export declare const AUTO_RETRY_MS: number;
|
|
10
|
+
export declare const RELEASE_NOTES: (v: string) => string;
|
|
11
|
+
/** What `update.json` in the state directory holds. Every field is optional; a broken file reads as `{}`. */
|
|
12
|
+
export interface UpdateCache {
|
|
13
|
+
/** When the registry last answered. */
|
|
14
|
+
checkedAt?: string;
|
|
15
|
+
/** When a check was last tried, answered or not. */
|
|
16
|
+
triedAt?: string;
|
|
17
|
+
/** The `latest` dist-tag it answered. */
|
|
18
|
+
latest?: string;
|
|
19
|
+
/** When a background check last looked at Auto Update (installed, or waited for a running session). */
|
|
20
|
+
autoTriedAt?: string;
|
|
21
|
+
/** The last Update Notice the CLI printed. */
|
|
22
|
+
notified?: {
|
|
23
|
+
version: string;
|
|
24
|
+
at: string;
|
|
25
|
+
};
|
|
26
|
+
/** The last Auto Update; `shown` once the CLI has said so. */
|
|
27
|
+
auto?: {
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
at: string;
|
|
31
|
+
ok: boolean;
|
|
32
|
+
message: string;
|
|
33
|
+
shown?: boolean;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export type InstallKind = 'npm' | 'pnpm' | 'bun' | 'volta' | 'npx' | 'project' | 'source';
|
|
37
|
+
export interface InstallMethod {
|
|
38
|
+
kind: InstallKind;
|
|
39
|
+
/** The installed package folder (real path). */
|
|
40
|
+
packageDir: string;
|
|
41
|
+
/** True when sideby can run the Update itself (npm, pnpm or bun global, and npm's folders are writable). */
|
|
42
|
+
canUpdate: boolean;
|
|
43
|
+
/** Why it cannot, in a phrase that follows "sideby cannot update itself: ". */
|
|
44
|
+
reason?: string;
|
|
45
|
+
/** npm only: the prefix whose `lib/node_modules` holds sideby. */
|
|
46
|
+
prefix?: string;
|
|
47
|
+
}
|
|
48
|
+
/** Swapped in tests through `bindUpdateDeps`; the real machine otherwise. */
|
|
49
|
+
export interface UpdateDeps {
|
|
50
|
+
fetch: typeof fetch;
|
|
51
|
+
now: () => Date;
|
|
52
|
+
/** Real path of the running sideby package. */
|
|
53
|
+
packageDir: () => Promise<string>;
|
|
54
|
+
writable: (dir: string) => Promise<boolean>;
|
|
55
|
+
alive: (pid: number) => boolean;
|
|
56
|
+
/** Runs a program, streaming its output to `onOutput`; resolves with the exit code and the whole output. */
|
|
57
|
+
exec: (file: string, args: readonly string[], opts: {
|
|
58
|
+
env: Env;
|
|
59
|
+
onOutput?: (text: string) => void;
|
|
60
|
+
}) => Promise<{
|
|
61
|
+
code: number;
|
|
62
|
+
output: string;
|
|
63
|
+
}>;
|
|
64
|
+
/** Starts `sideby ui --background --no-open --port <port>` from the newly installed code, detached; false when it could not start. */
|
|
65
|
+
restartPanel: (env: Env, port: number) => Promise<boolean>;
|
|
66
|
+
}
|
|
67
|
+
/** Internal, for tests: replaces parts of the machine. Not part of the plugin or Portal contract. */
|
|
68
|
+
export declare function bindUpdateDeps(deps: Partial<UpdateDeps>): () => void;
|
|
69
|
+
/** -1, 0 or 1 like a sort comparator; a prerelease sorts before its release. Null when either is not a version. */
|
|
70
|
+
export declare function compareVersions(a: string, b: string): number | null;
|
|
71
|
+
/** `latest` is newer than `current`. */
|
|
72
|
+
export declare const isNewer: (latest: string | undefined, current: string) => latest is string;
|
|
73
|
+
/**
|
|
74
|
+
* The jump keeps the major version (0.11 → 0.12, 1.2 → 1.9). Auto Update only takes these; 0.x → 1.0 or 1.x → 2.0
|
|
75
|
+
* only gets a notice.
|
|
76
|
+
*/
|
|
77
|
+
export declare function sameLine(from: string, to: string): boolean;
|
|
78
|
+
/** True in CI, where a notice is noise and a check is a request nobody reads. */
|
|
79
|
+
export declare const inCi: (env: Env) => boolean;
|
|
80
|
+
type UpdateSettings = {
|
|
81
|
+
update?: {
|
|
82
|
+
check?: boolean;
|
|
83
|
+
auto?: boolean;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
/** Why Update Checks are off, or null when they are on (spec §3.23). */
|
|
87
|
+
export declare function checksOff(env: Env, config: UpdateSettings): string | null;
|
|
88
|
+
export declare function readUpdateCache(paths: Paths): Promise<UpdateCache>;
|
|
89
|
+
/**
|
|
90
|
+
* Merges a change into the cache file. The file is read again first, and `change` may be a function of what is there
|
|
91
|
+
* now, so a writer that decides from an older read (the CLI marking an Auto Update shown) never overwrites a newer
|
|
92
|
+
* field another process wrote meanwhile.
|
|
93
|
+
*/
|
|
94
|
+
export declare function updateUpdateCache(paths: Paths, change: Partial<UpdateCache> | ((now: UpdateCache) => Partial<UpdateCache>)): Promise<UpdateCache>;
|
|
95
|
+
/** A check is due: no answer within a day, and no attempt within the hour (so being offline costs one try an hour). */
|
|
96
|
+
export declare function checkDue(cache: UpdateCache, now: Date): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Auto Update is on and the cache names a newer version it has not installed: never tried, or a failed try a day
|
|
99
|
+
* ago. Lets the CLI start a background check even when a Panel already ran today's check.
|
|
100
|
+
*/
|
|
101
|
+
export declare function autoPending(cache: UpdateCache, current: string, config: UpdateSettings, now: Date): boolean;
|
|
102
|
+
/** Asks the registry for the `latest` dist-tag. Throws with a message that names the registry and what to do. */
|
|
103
|
+
export declare function fetchLatest(env: Env): Promise<string>;
|
|
104
|
+
/** Runs one Update Check and records it, answered or not. Never throws. */
|
|
105
|
+
export declare function refreshUpdateCheck(paths: Paths, env: Env): Promise<UpdateCache>;
|
|
106
|
+
/**
|
|
107
|
+
* The Update Notice line the CLI may print now, or null. Once per new version per day; `current` is the running
|
|
108
|
+
* version.
|
|
109
|
+
*/
|
|
110
|
+
export declare function noticeLine(cache: UpdateCache, current: string, now: Date): string | null;
|
|
111
|
+
/** What the CLI says once about the last Auto Update, or null. */
|
|
112
|
+
export declare function autoUpdateLine(cache: UpdateCache): string | null;
|
|
113
|
+
/**
|
|
114
|
+
* What the CLI does after a command (spec §3.23), decided from the cache: the lines to print (the Auto Update result,
|
|
115
|
+
* then the Update Notice), the change that records them, and whether to start a background check. The caller prints,
|
|
116
|
+
* writes `record` with `updateUpdateCache` and starts the check; nothing here touches the machine.
|
|
117
|
+
*/
|
|
118
|
+
export declare function afterCommandPlan(cache: UpdateCache, current: string, config: UpdateSettings, env: Env, now: Date): {
|
|
119
|
+
lines: string[];
|
|
120
|
+
record: ((cur: UpdateCache) => Partial<UpdateCache>) | null;
|
|
121
|
+
startCheck: boolean;
|
|
122
|
+
};
|
|
123
|
+
/** Where the running copy comes from (spec §3.23 table); reads only paths. */
|
|
124
|
+
export declare function installMethod(env: Env): Promise<InstallMethod>;
|
|
125
|
+
/**
|
|
126
|
+
* The command an Update runs (or that the user runs) for `version`; `run` is set only when sideby may run it. With
|
|
127
|
+
* `SIDEBY_UPDATE_REGISTRY`, installs use that registry too, so the version the check found is the one fetched.
|
|
128
|
+
*/
|
|
129
|
+
export declare function updateCommand(method: InstallMethod, version: string, env?: Env): {
|
|
130
|
+
text: string;
|
|
131
|
+
run?: {
|
|
132
|
+
file: string;
|
|
133
|
+
args: string[];
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Records that this process runs a Host (`run`, `login`, `next`, `resume`) so an Auto Update waits for it; returns the
|
|
138
|
+
* function that removes the record. Failing to write the record never stops a Launch.
|
|
139
|
+
*/
|
|
140
|
+
export declare function markRunning(paths: Paths, pid?: number): Promise<() => Promise<void>>;
|
|
141
|
+
/** Pids of sideby processes that run a Host now; records of processes that ended are removed. */
|
|
142
|
+
export declare function runningSessions(paths: Paths): Promise<number[]>;
|
|
143
|
+
export interface UpdatePlan {
|
|
144
|
+
current: string;
|
|
145
|
+
latest: string;
|
|
146
|
+
available: boolean;
|
|
147
|
+
method: InstallMethod;
|
|
148
|
+
/** The command for the user to see; `canRun` says whether sideby runs it after a confirmation. */
|
|
149
|
+
command: string;
|
|
150
|
+
canRun: boolean;
|
|
151
|
+
releaseNotes: string;
|
|
152
|
+
}
|
|
153
|
+
export declare function planFrom(method: InstallMethod, current: string, latest: string, env?: Env): UpdatePlan;
|
|
154
|
+
/** The Install Method as outputs show it: no folder of this machine. */
|
|
155
|
+
export declare const shownMethod: (m: InstallMethod) => {
|
|
156
|
+
kind: InstallKind;
|
|
157
|
+
canUpdate: boolean;
|
|
158
|
+
reason?: string | undefined;
|
|
159
|
+
};
|
|
160
|
+
/** The plan as `sideby update --json` and the Panel show it (schemas/update.json). */
|
|
161
|
+
export declare function shownPlan(plan: UpdatePlan): {
|
|
162
|
+
current: string;
|
|
163
|
+
latest: string;
|
|
164
|
+
available: boolean;
|
|
165
|
+
method: {
|
|
166
|
+
kind: InstallKind;
|
|
167
|
+
canUpdate: boolean;
|
|
168
|
+
reason?: string | undefined;
|
|
169
|
+
};
|
|
170
|
+
command: string;
|
|
171
|
+
canRun: boolean;
|
|
172
|
+
releaseNotes: string;
|
|
173
|
+
};
|
|
174
|
+
/** Asks the registry now (whatever the switches say: the user asked) and returns what an Update would do. */
|
|
175
|
+
export declare function planUpdate(paths: Paths, env: Env): Promise<UpdatePlan>;
|
|
176
|
+
export interface UpdateResult {
|
|
177
|
+
ok: boolean;
|
|
178
|
+
/** Exit code of the install command; absent when it did not run. */
|
|
179
|
+
code?: number;
|
|
180
|
+
output: string;
|
|
181
|
+
message: string;
|
|
182
|
+
/** A background Panel was running and is starting again from the new code. */
|
|
183
|
+
restartedPanel: boolean;
|
|
184
|
+
/** Another Update held the lock; nothing ran. */
|
|
185
|
+
busy?: boolean;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Runs the Update the plan names, then starts a running background Panel again from the new code. The caller has
|
|
189
|
+
* shown the plan and got a confirmation (or this is an Auto Update). One Update runs at a time on this machine (the
|
|
190
|
+
* CLI, a Panel and an Auto Update share the lock). `panelPort` gives the port of the background Panel to restart, or
|
|
191
|
+
* null when none runs. A successful Update is recorded as said, so no notice for it follows.
|
|
192
|
+
*/
|
|
193
|
+
export declare function applyUpdate(paths: Paths, plan: UpdatePlan, env: Env, opts?: {
|
|
194
|
+
onOutput?: (text: string) => void;
|
|
195
|
+
panelPort?: () => Promise<number | null>;
|
|
196
|
+
}): Promise<UpdateResult>;
|
|
197
|
+
/** What keeps an Auto Update from installing `plan` until the user acts (an install it cannot update, a major version). */
|
|
198
|
+
export declare function lastingBlocker(plan: UpdatePlan): string | null;
|
|
199
|
+
/**
|
|
200
|
+
* Whether an Auto Update may install `plan` now (spec §3.23), and if not, why. Running sessions are looked up
|
|
201
|
+
* last because that removes records of ended processes.
|
|
202
|
+
*/
|
|
203
|
+
export declare function autoUpdateBlocker(paths: Paths, plan: UpdatePlan): Promise<string | null>;
|
|
204
|
+
/**
|
|
205
|
+
* Body of the detached `sideby update-check`: an Update Check when one is due, then an Auto Update when config
|
|
206
|
+
* `update.auto` is on and nothing blocks it. Only one runs at a time; it never throws.
|
|
207
|
+
*/
|
|
208
|
+
export declare function backgroundCheck(paths: Paths, env: Env, config: UpdateSettings, panelPort: () => Promise<number | null>): Promise<void>;
|
|
209
|
+
export {};
|