piu-piu-cli 0.1.0 → 0.1.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to piu-piu. Versions follow [semver](https://semver.org).
4
4
 
5
+ ## 0.1.1 (2026-10-05)
6
+
7
+ - Fix piu-piu setup failing with launchd "Bootstrap failed: 5" when the service was already loaded
8
+
5
9
  ## 0.1.0 (2026-10-05)
6
10
 
7
11
  First release.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://unpkg.com/piu-piu-cli@0.1.0/docs/banner.svg" alt="piu-piu: a 3D office for your Claude Code agents" width="100%">
2
+ <img src="https://unpkg.com/piu-piu-cli@0.1.1/docs/banner.svg" alt="piu-piu: a 3D office for your Claude Code agents" width="100%">
3
3
  </p>
4
4
 
5
5
  <p align="center">
package/bin/piu-piu.mjs CHANGED
@@ -33,6 +33,22 @@ const fail = (msg) => {
33
33
  process.exit(1);
34
34
  };
35
35
  const quiet = (cmd, args) => spawnSync(cmd, args, { stdio: 'pipe', encoding: 'utf8' });
36
+ const sleepSync = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
37
+
38
+ // launchd unloads asynchronously: loading again too soon fails with
39
+ // "Bootstrap failed: 5". Unload, wait until it's gone, then load (retrying once).
40
+ function launchdUnload(target) {
41
+ quiet('launchctl', ['bootout', target]);
42
+ for (let i = 0; i < 40 && quiet('launchctl', ['print', target]).status === 0; i++) sleepSync(250);
43
+ }
44
+ function launchdLoad(domain, plist) {
45
+ let r = quiet('launchctl', ['bootstrap', domain, plist]);
46
+ for (let i = 0; i < 3 && r.status !== 0; i++) {
47
+ sleepSync(1000);
48
+ r = quiet('launchctl', ['bootstrap', domain, plist]);
49
+ }
50
+ return r;
51
+ }
36
52
 
37
53
  function claudeBinary() {
38
54
  for (const p of [join(homedir(), '.local', 'bin', 'claude'), '/opt/homebrew/bin/claude', '/usr/local/bin/claude']) if (existsSync(p)) return p;
@@ -138,9 +154,9 @@ function serviceInstall() {
138
154
  if (MAC) {
139
155
  const domain = `gui/${process.getuid()}`;
140
156
  // the pre-rename service, if this Mac still has it
141
- quiet('launchctl', ['bootout', `${domain}/${LEGACY_LABEL}`]);
157
+ launchdUnload(`${domain}/${LEGACY_LABEL}`);
142
158
  rmSync(join(homedir(), 'Library', 'LaunchAgents', `${LEGACY_LABEL}.plist`), { force: true });
143
- quiet('launchctl', ['bootout', `${domain}/${LABEL}`]);
159
+ launchdUnload(`${domain}/${LABEL}`);
144
160
  const env = [['PATH', servicePath()], ['HOME', homedir()], ...passEnv()].map(([k, v]) => ` <key>${esc(k)}</key><string>${esc(v)}</string>`).join('\n');
145
161
  mkdirSync(dirname(PLIST), { recursive: true });
146
162
  mkdirSync(dirname(LOG), { recursive: true });
@@ -170,8 +186,8 @@ ${env}
170
186
  </plist>
171
187
  `,
172
188
  );
173
- const r = quiet('launchctl', ['bootstrap', domain, PLIST]);
174
- if (r.status !== 0) fail(`launchctl could not start the service: ${r.stderr}`);
189
+ const r = launchdLoad(domain, PLIST);
190
+ if (r.status !== 0) fail(`launchctl could not start the service: ${r.stderr.trim()}. Try: piu-piu start`);
175
191
  } else {
176
192
  if (quiet('systemctl', ['--user', '--version']).status !== 0) fail('No systemd user session here. Run the office in the foreground instead: piu-piu run');
177
193
  mkdirSync(dirname(UNIT), { recursive: true });
@@ -204,7 +220,7 @@ WantedBy=default.target
204
220
 
205
221
  function serviceUninstall() {
206
222
  if (MAC) {
207
- quiet('launchctl', ['bootout', `gui/${process.getuid()}/${LABEL}`]);
223
+ launchdUnload(`gui/${process.getuid()}/${LABEL}`);
208
224
  rmSync(PLIST, { force: true });
209
225
  } else {
210
226
  quiet('systemctl', ['--user', 'disable', '--now', 'piu-piu.service']);
@@ -231,7 +247,7 @@ function open() {
231
247
  async function start() {
232
248
  if (await state()) return say(c.ok('✓'), `piu-piu is running at ${c.b(URL)}`);
233
249
  if (!serviceInstalled()) serviceInstall();
234
- else if (MAC) quiet('launchctl', ['bootstrap', `gui/${process.getuid()}`, PLIST]);
250
+ else if (MAC) launchdLoad(`gui/${process.getuid()}`, PLIST);
235
251
  else quiet('systemctl', ['--user', 'start', 'piu-piu.service']);
236
252
  if (!(await waitUp())) fail(`piu-piu didn't come up. Check the logs: piu-piu logs`);
237
253
  say(c.ok('✓'), `piu-piu is running at ${c.b(URL)}`);
@@ -239,7 +255,7 @@ async function start() {
239
255
 
240
256
  async function stop() {
241
257
  await guardBusy('Stopping');
242
- if (MAC) quiet('launchctl', ['bootout', `gui/${process.getuid()}/${LABEL}`]);
258
+ if (MAC) launchdUnload(`gui/${process.getuid()}/${LABEL}`);
243
259
  else quiet('systemctl', ['--user', 'stop', 'piu-piu.service']);
244
260
  say(c.ok('✓'), 'piu-piu stopped. Start it again with: piu-piu start');
245
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "piu-piu-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A 3D office for your Claude Code agents: watch every session work, talk to them, approve, resume, and drive the real Claude Code from one place.",
5
5
  "type": "module",
6
6
  "bin": {