agentstuff 0.1.2 → 0.1.3

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +207 -55
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -11,7 +11,7 @@ bun add --global agentstuff
11
11
  agentstuff setup
12
12
  ```
13
13
 
14
- Setup opens an interactive wizard. It can perform a complete setup, repair only the desktop integration, pair another iPhone, or change the terminal shortcut. The complete flow checks prerequisites, installs the background service and terminal integration, selects an available short command, prepares the mobile app, displays the private pairing QR, and waits for connection confirmation.
14
+ Setup starts with two paths: a complete guided setup or a fast desktop repair. Complete setup walks through the iPhone, desktop integration, and terminal shortcut as separate numbered steps. Existing configuration is detected at every step, so you can show the current pairing code, rotate it, keep a healthy integration, or change the shortcut without redoing unrelated setup.
15
15
 
16
16
  When run from an Agentstuff development checkout, the wizard can build and install the iOS development app with Xcode. A configured `AGENTSTUFF_MOBILE_INSTALL_URL` enables the public mobile-download option. Until a TestFlight or App Store build is configured, npm cannot install a signed iOS app by itself.
17
17
 
package/dist/index.js CHANGED
@@ -39569,6 +39569,65 @@ var runWith2 = (command, config) => {
39569
39569
  yield* provideContext2(program, services);
39570
39570
  }, catchFilter2((error2) => isCliError(error2) && error2._tag === "ShowHelp" ? succeed2(error2) : fail2(error2), (error2) => andThen2(showHelp(command, error2), fail6(error2))), catchFilter2((e) => isQuitError(e) ? succeed2(e) : fail2(e), (_) => interrupt3));
39571
39571
  };
39572
+ // package.json
39573
+ var package_default = {
39574
+ name: "agentstuff",
39575
+ version: "0.1.3",
39576
+ description: "Continue terminal agent sessions securely from your phone",
39577
+ license: "UNLICENSED",
39578
+ type: "module",
39579
+ os: ["darwin"],
39580
+ engines: {
39581
+ bun: ">=1.3.10"
39582
+ },
39583
+ files: [
39584
+ "dist",
39585
+ "README.md"
39586
+ ],
39587
+ bin: {
39588
+ agentstuff: "dist/index.js"
39589
+ },
39590
+ repository: {
39591
+ type: "git",
39592
+ url: "git+https://github.com/swarajbachu/agent-stuff.git",
39593
+ directory: "apps/daemon"
39594
+ },
39595
+ homepage: "https://github.com/swarajbachu/agent-stuff#readme",
39596
+ bugs: "https://github.com/swarajbachu/agent-stuff/issues",
39597
+ keywords: [
39598
+ "ai",
39599
+ "agent",
39600
+ "mobile",
39601
+ "terminal",
39602
+ "bun"
39603
+ ],
39604
+ scripts: {
39605
+ build: "bun build src/index.ts --outdir dist --target bun",
39606
+ dev: "bun --watch src/index.ts start",
39607
+ prepack: "bun run build",
39608
+ start: "bun src/index.ts start",
39609
+ "test:relay-client": "bun scripts/relay-client.ts",
39610
+ "spike:claude-resume": "bun scripts/claude-resume.ts",
39611
+ "spike:codex-app-server": "bun scripts/codex-app-server.ts",
39612
+ test: "bun test src",
39613
+ typecheck: "tsc -p tsconfig.json --noEmit"
39614
+ },
39615
+ devDependencies: {
39616
+ "@agentstuff/crypto": "workspace:*",
39617
+ "@agentstuff/protocol": "workspace:*",
39618
+ "@anthropic-ai/claude-agent-sdk": "^0.3.0",
39619
+ "@effect/platform-bun": "4.0.0-beta.97",
39620
+ "@effect/sql-sqlite-bun": "4.0.0-beta.97",
39621
+ "@modelcontextprotocol/sdk": "^1.29.0",
39622
+ chokidar: "^4.0.0",
39623
+ effect: "4.0.0-beta.97",
39624
+ "qrcode-terminal": "^0.12.0",
39625
+ zod: "4.4.3",
39626
+ "@types/bun": "^1.3.14",
39627
+ "@types/qrcode-terminal": "^0.12.2"
39628
+ }
39629
+ };
39630
+
39572
39631
  // src/adapters/AdapterRegistry.ts
39573
39632
  class AdapterNotFound extends TaggedErrorClass()("AdapterNotFound", {
39574
39633
  provider: String4
@@ -82105,6 +82164,23 @@ var installedShellCommand = fn2("ShellIntegration.status")(function* (path5 = sh
82105
82164
  });
82106
82165
 
82107
82166
  // src/setup.ts
82167
+ var pairingActions = (hasExistingPairing) => hasExistingPairing ? ["show", "rotate", "keep"] : ["create"];
82168
+ var pairingPromptChoice = (action2, phoneConnected) => {
82169
+ switch (action2) {
82170
+ case "create":
82171
+ return { title: "Create a pairing code", value: action2, description: "Securely connect this Mac and iPhone" };
82172
+ case "show":
82173
+ return { title: "Show the current pairing code", value: action2, description: "Existing paired phones keep working" };
82174
+ case "rotate":
82175
+ return { title: "Generate a new pairing code", value: action2, description: "Revokes every previously shared code" };
82176
+ case "keep":
82177
+ return {
82178
+ title: "Keep the current pairing",
82179
+ value: action2,
82180
+ description: phoneConnected ? "An authenticated iPhone is connected" : "Skip showing a pairing code"
82181
+ };
82182
+ }
82183
+ };
82108
82184
  var setupErrorMessage = (error52) => {
82109
82185
  if (isObject(error52) && hasProperty(error52, "_tag") && error52._tag === "QuitError") {
82110
82186
  return "Setup cancelled.";
@@ -82126,8 +82202,8 @@ var MobileInstallUrl = String4.pipe(check(makeFilter2((value2) => {
82126
82202
  var decodeMobileInstallUrl = decodeUnknownOption2(MobileInstallUrl);
82127
82203
  var setupSections = (mode) => ({
82128
82204
  desktop: mode === "complete" || mode === "repair",
82129
- shortcut: mode === "complete" || mode === "shortcut",
82130
- phone: mode === "complete" || mode === "pair"
82205
+ shortcut: mode === "complete",
82206
+ phone: mode === "complete"
82131
82207
  });
82132
82208
  var findMobileProject = (start = process.cwd(), exists = existsSync3) => {
82133
82209
  let directory4 = resolve6(start);
@@ -82149,10 +82225,8 @@ var selectMode = fn2("Setup.selectMode")(function* (yes) {
82149
82225
  return yield* exports_Prompt.select({
82150
82226
  message: "What would you like to set up?",
82151
82227
  choices: [
82152
- { title: "Complete setup", value: "complete", description: "Desktop, terminal shortcut, and iPhone pairing" },
82153
- { title: "Repair desktop integration", value: "repair", description: "Reinstall the background service and live channel" },
82154
- { title: "Pair an iPhone", value: "pair", description: "Install or open the mobile app and show a pairing code" },
82155
- { title: "Configure terminal shortcut", value: "shortcut", description: "Choose the command used to start connected sessions" }
82228
+ { title: "Complete setup", value: "complete", description: "Walk through iPhone, desktop, and terminal setup" },
82229
+ { title: "Repair setup", value: "repair", description: "Reinstall the background service and live terminal integration" }
82156
82230
  ]
82157
82231
  });
82158
82232
  });
@@ -82165,10 +82239,27 @@ var runCommand = fn2("Setup.runCommand")(function* (executable2, arguments_, cwd
82165
82239
  stderr: "inherit"
82166
82240
  }));
82167
82241
  });
82168
- var configureShortcut = fn2("Setup.configureShortcut")(function* (options) {
82242
+ var configureShortcut = fn2("Setup.configureShortcut")(function* (options, existingCommand) {
82169
82243
  if (options.noAlias)
82170
82244
  return none2();
82171
82245
  let commandName = getOrUndefined(options.alias);
82246
+ if (!options.yes && commandName === undefined && existingCommand !== undefined) {
82247
+ const action2 = yield* exports_Prompt.select({
82248
+ message: `Terminal shortcut \`${existingCommand}\` is already configured.`,
82249
+ choices: [
82250
+ { title: `Keep ${existingCommand}`, value: "keep", description: "No shell changes needed" },
82251
+ { title: "Choose a different shortcut", value: "change", description: "Replace the current Agentstuff shortcut" }
82252
+ ]
82253
+ });
82254
+ if (action2 === "keep") {
82255
+ yield* log(`\u2713 Terminal shortcut: ${existingCommand}`);
82256
+ return some2(existingCommand);
82257
+ }
82258
+ commandName = yield* exports_Prompt.text({
82259
+ message: "New shortcut name",
82260
+ default: existingCommand === "agt" ? "astuff" : "agt"
82261
+ });
82262
+ }
82172
82263
  if (!options.yes && commandName === undefined) {
82173
82264
  const choice5 = yield* exports_Prompt.select({
82174
82265
  message: "How should connected terminal sessions start?",
@@ -82259,69 +82350,130 @@ var runGuidedSetup = fn2("Setup.run")(function* (options) {
82259
82350
  yield* log("");
82260
82351
  const mode = yield* selectMode(options.yes);
82261
82352
  const sections = setupSections(mode);
82262
- if (sections.desktop && !claudeFound) {
82353
+ if (mode === "repair" && !claudeFound) {
82263
82354
  yield* error("Claude CLI is required for live terminal integration. Install it, then rerun setup.");
82264
82355
  return;
82265
82356
  }
82266
- let rotate = options.reset;
82267
- if (sections.phone && isSome2(existing) && !options.yes && !options.reset) {
82268
- rotate = yield* exports_Prompt.select({
82269
- message: "Which pairing credential should this phone use?",
82270
- choices: [
82271
- { title: "Keep the current pairing", value: false, description: "Existing paired phones keep working" },
82272
- { title: "Rotate the pairing", value: true, description: "Revokes every previously shared pairing code" }
82273
- ]
82274
- });
82275
- }
82276
82357
  const relayUrl = getOrUndefined(options.relay);
82358
+ const presencePath = join8(options.dataDirectory, "mobile-presence.json");
82359
+ let daemonRunning = daemon.installed;
82360
+ let integrationReady = integrationInstalled;
82361
+ let phoneConnected = yield* loadMobilePresence(presencePath).pipe(catch_2(() => succeed7(false)));
82277
82362
  let configured = none2();
82278
- if (sections.desktop || sections.phone) {
82279
- configured = some2(yield* configurePairing({
82363
+ const ensurePairing = fn2("Setup.ensurePairing")(function* (reset2) {
82364
+ const result3 = yield* configurePairing({
82280
82365
  ...relayUrl === undefined ? {} : { relayUrl },
82281
- reset: rotate
82282
- }));
82283
- }
82284
- if (!sections.desktop && isSome2(configured) && configured.value.changed && daemon.installed) {
82285
- yield* restartDaemon();
82286
- }
82287
- if (sections.desktop) {
82288
- yield* installTerminalIntegration(undefined, true);
82289
- yield* log("\u2713 Live terminal integration");
82290
- yield* installDaemon({ dataDirectory: options.dataDirectory, load: true });
82291
- yield* log("\u2713 Background service");
82292
- } else if (sections.phone && !daemon.installed) {
82293
- yield* installDaemon({ dataDirectory: options.dataDirectory, load: true });
82294
- yield* log("\u2713 Background service");
82295
- }
82296
- const shortcut = sections.shortcut ? yield* configureShortcut(options) : none2();
82297
- let phoneConnected = false;
82298
- if (sections.phone && !options.noPair && isSome2(configured) && (yield* prepareMobileApp(options.yes))) {
82366
+ reset: reset2
82367
+ });
82368
+ if (result3.changed) {
82369
+ phoneConnected = false;
82370
+ yield* saveMobilePresence(false, presencePath);
82371
+ }
82372
+ configured = some2(result3);
82373
+ return result3;
82374
+ });
82375
+ if (sections.phone) {
82299
82376
  yield* log(`
82377
+ Step 1 of 3 \xB7 Connect your iPhone
82378
+ `);
82379
+ if (options.noPair) {
82380
+ yield* log("\xB7 Phone pairing skipped by --no-pair");
82381
+ } else {
82382
+ const hasExistingPairing = isSome2(existing);
82383
+ const pairingAction = options.reset ? "rotate" : options.yes ? hasExistingPairing ? "show" : "create" : hasExistingPairing ? yield* exports_Prompt.select({
82384
+ message: "Mobile pairing is already configured. What should we do?",
82385
+ choices: pairingActions(true).map((action2) => pairingPromptChoice(action2, phoneConnected))
82386
+ }) : "create";
82387
+ if (pairingAction === "keep") {
82388
+ yield* log(`\u2713 Current pairing kept${phoneConnected ? " \xB7 iPhone connected" : ""}`);
82389
+ } else {
82390
+ const pairing = yield* ensurePairing(pairingAction === "rotate");
82391
+ if (pairing.changed && daemonRunning)
82392
+ yield* restartDaemon();
82393
+ if (!daemonRunning) {
82394
+ yield* installDaemon({ dataDirectory: options.dataDirectory, load: true });
82395
+ daemonRunning = true;
82396
+ yield* log("\u2713 Background service started for pairing");
82397
+ }
82398
+ if (yield* prepareMobileApp(options.yes)) {
82399
+ yield* log(`
82300
82400
  On your iPhone, open Agentstuff \u2192 Connect your Mac \u2192 Scan pairing code.
82301
82401
  `);
82302
- yield* renderQr(pairingUrl(configured.value.pairing));
82303
- yield* log("Waiting for an authenticated iPhone connection\u2026");
82304
- phoneConnected = yield* waitForMobilePresence(join8(options.dataDirectory, "mobile-presence.json"), options.yes ? 10 : 60);
82305
- while (!phoneConnected && !options.yes) {
82306
- const action2 = yield* exports_Prompt.select({
82307
- message: "The iPhone has not connected yet.",
82402
+ yield* renderQr(pairingUrl(pairing.pairing));
82403
+ yield* log("Waiting for an authenticated iPhone connection\u2026");
82404
+ phoneConnected = yield* waitForMobilePresence(presencePath, options.yes ? 10 : 60);
82405
+ while (!phoneConnected && !options.yes) {
82406
+ const action2 = yield* exports_Prompt.select({
82407
+ message: "The iPhone has not connected yet.",
82408
+ choices: [
82409
+ { title: "Wait another 30 seconds", value: "retry", description: "Keep the app open after scanning" },
82410
+ { title: "Continue setup and pair later", value: "later", description: "The pairing code remains ready" }
82411
+ ]
82412
+ });
82413
+ if (action2 === "later")
82414
+ break;
82415
+ phoneConnected = yield* waitForMobilePresence(presencePath, 60);
82416
+ }
82417
+ yield* log(phoneConnected ? "\u2713 Authenticated iPhone connected" : "! Pairing is ready. Scan the code from Agentstuff when your phone is available.");
82418
+ }
82419
+ }
82420
+ }
82421
+ }
82422
+ if (sections.desktop) {
82423
+ yield* log(mode === "complete" ? `
82424
+ Step 2 of 3 \xB7 Configure terminal integration
82425
+ ` : `
82426
+ Repairing desktop integration
82427
+ `);
82428
+ if (!claudeFound) {
82429
+ yield* log("! Terminal integration skipped because the required CLI is not installed.");
82430
+ yield* log(" Install it, then choose Repair setup.");
82431
+ } else {
82432
+ const healthy = integrationReady && daemonRunning;
82433
+ const desktopAction = mode === "repair" || options.yes ? "install" : healthy ? yield* exports_Prompt.select({
82434
+ message: "Terminal integration is already running.",
82308
82435
  choices: [
82309
- { title: "Wait another 30 seconds", value: "retry", description: "Keep the app open after scanning" },
82310
- { title: "Finish pairing later", value: "later", description: "Your desktop setup is already complete" }
82436
+ { title: "Keep the current integration", value: "keep", description: "No changes needed" },
82437
+ { title: "Reinstall the integration", value: "install", description: "Refresh the service and live terminal channel" }
82438
+ ]
82439
+ }) : yield* exports_Prompt.select({
82440
+ message: "Terminal integration needs attention.",
82441
+ choices: [
82442
+ {
82443
+ title: "Configure it now",
82444
+ value: "install",
82445
+ description: integrationReady ? "Restart the background service" : "Install the live terminal channel and background service"
82446
+ },
82447
+ { title: "Skip for now", value: "skip", description: "Finish from Repair setup later" }
82311
82448
  ]
82312
82449
  });
82313
- if (action2 === "later")
82314
- break;
82315
- phoneConnected = yield* waitForMobilePresence(join8(options.dataDirectory, "mobile-presence.json"), 60);
82316
- }
82317
- if (!phoneConnected) {
82318
- yield* log("! Pairing is ready. Open Agentstuff and scan the code when your phone is available.");
82450
+ if (desktopAction === "install") {
82451
+ if (isNone2(configured))
82452
+ yield* ensurePairing(options.reset && options.noPair);
82453
+ yield* installTerminalIntegration(undefined, true);
82454
+ integrationReady = true;
82455
+ yield* log("\u2713 Live terminal integration configured");
82456
+ yield* installDaemon({ dataDirectory: options.dataDirectory, load: true });
82457
+ daemonRunning = true;
82458
+ yield* log("\u2713 Background service running");
82459
+ } else if (desktopAction === "keep") {
82460
+ yield* log("\u2713 Current terminal integration kept");
82461
+ } else {
82462
+ yield* log("\xB7 Terminal integration skipped");
82463
+ }
82319
82464
  }
82320
82465
  }
82321
- const launchCommand = getOrElse(shortcut, () => "agentstuff claude");
82466
+ let shortcut = none2();
82467
+ if (sections.shortcut) {
82468
+ yield* log(`
82469
+ Step 3 of 3 \xB7 Configure terminal shortcut
82470
+ `);
82471
+ shortcut = yield* configureShortcut(options, shellCommand);
82472
+ }
82473
+ const launchCommand = getOrElse(shortcut, () => shellCommand ?? "agentstuff claude");
82322
82474
  yield* log(`
82323
82475
  Ready.`);
82324
- if (sections.desktop)
82476
+ if (integrationReady && daemonRunning)
82325
82477
  yield* log(" \u2713 Desktop integration running");
82326
82478
  if (phoneConnected)
82327
82479
  yield* log(" \u2713 iPhone connected");
@@ -85030,5 +85182,5 @@ if (rawCommand === "terminal" || rawCommand === "claude") {
85030
85182
  process.exitCode = exitCode;
85031
85183
  })), asVoid2, provide4(exports_BunServices.layer)));
85032
85184
  } else {
85033
- cli.pipe(exports_Command.run({ version: "0.1.2" }), provide4(exports_BunServices.layer), exports_BunRuntime.runMain);
85185
+ cli.pipe(exports_Command.run({ version: package_default.version }), provide4(exports_BunServices.layer), exports_BunRuntime.runMain);
85034
85186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentstuff",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Continue terminal agent sessions securely from your phone",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  "README.md"
14
14
  ],
15
15
  "bin": {
16
- "agentstuff": "./dist/index.js"
16
+ "agentstuff": "dist/index.js"
17
17
  },
18
18
  "repository": {
19
19
  "type": "git",
@@ -41,8 +41,8 @@
41
41
  "typecheck": "tsc -p tsconfig.json --noEmit"
42
42
  },
43
43
  "devDependencies": {
44
- "@agentstuff/crypto": "0.0.0",
45
- "@agentstuff/protocol": "0.0.0",
44
+ "@agentstuff/crypto": "workspace:*",
45
+ "@agentstuff/protocol": "workspace:*",
46
46
  "@anthropic-ai/claude-agent-sdk": "^0.3.0",
47
47
  "@effect/platform-bun": "4.0.0-beta.97",
48
48
  "@effect/sql-sqlite-bun": "4.0.0-beta.97",