agentstuff 0.1.2 → 0.1.4

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 +2 -2
  2. package/dist/index.js +230 -72
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -11,9 +11,9 @@ 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
- 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.
16
+ The public wizard sends users to the App Store for the signed iPhone app. Source-build controls are hidden from normal setup and are available to contributors only when `AGENTSTUFF_DEVELOPER_SETUP=1` is explicitly set in a development checkout.
17
17
 
18
18
  Open a new terminal and start a live session:
19
19
 
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.4",
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.";
@@ -82124,10 +82200,11 @@ var MobileInstallUrl = String4.pipe(check(makeFilter2((value2) => {
82124
82200
  }
82125
82201
  }, { expected: "an HTTP(S) mobile install URL" })));
82126
82202
  var decodeMobileInstallUrl = decodeUnknownOption2(MobileInstallUrl);
82203
+ var decodeDeveloperSetup = decodeUnknownOption2(Literal2("1"));
82127
82204
  var setupSections = (mode) => ({
82128
82205
  desktop: mode === "complete" || mode === "repair",
82129
- shortcut: mode === "complete" || mode === "shortcut",
82130
- phone: mode === "complete" || mode === "pair"
82206
+ shortcut: mode === "complete",
82207
+ phone: mode === "complete"
82131
82208
  });
82132
82209
  var findMobileProject = (start = process.cwd(), exists = existsSync3) => {
82133
82210
  let directory4 = resolve6(start);
@@ -82149,10 +82226,8 @@ var selectMode = fn2("Setup.selectMode")(function* (yes) {
82149
82226
  return yield* exports_Prompt.select({
82150
82227
  message: "What would you like to set up?",
82151
82228
  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" }
82229
+ { title: "Complete setup", value: "complete", description: "Walk through iPhone, desktop, and terminal setup" },
82230
+ { title: "Repair setup", value: "repair", description: "Reinstall the background service and live terminal integration" }
82156
82231
  ]
82157
82232
  });
82158
82233
  });
@@ -82165,10 +82240,27 @@ var runCommand = fn2("Setup.runCommand")(function* (executable2, arguments_, cwd
82165
82240
  stderr: "inherit"
82166
82241
  }));
82167
82242
  });
82168
- var configureShortcut = fn2("Setup.configureShortcut")(function* (options) {
82243
+ var configureShortcut = fn2("Setup.configureShortcut")(function* (options, existingCommand) {
82169
82244
  if (options.noAlias)
82170
82245
  return none2();
82171
82246
  let commandName = getOrUndefined(options.alias);
82247
+ if (!options.yes && commandName === undefined && existingCommand !== undefined) {
82248
+ const action2 = yield* exports_Prompt.select({
82249
+ message: `Terminal shortcut \`${existingCommand}\` is already configured.`,
82250
+ choices: [
82251
+ { title: `Keep ${existingCommand}`, value: "keep", description: "No shell changes needed" },
82252
+ { title: "Choose a different shortcut", value: "change", description: "Replace the current Agentstuff shortcut" }
82253
+ ]
82254
+ });
82255
+ if (action2 === "keep") {
82256
+ yield* log(`\u2713 Terminal shortcut: ${existingCommand}`);
82257
+ return some2(existingCommand);
82258
+ }
82259
+ commandName = yield* exports_Prompt.text({
82260
+ message: "New shortcut name",
82261
+ default: existingCommand === "agt" ? "astuff" : "agt"
82262
+ });
82263
+ }
82172
82264
  if (!options.yes && commandName === undefined) {
82173
82265
  const choice5 = yield* exports_Prompt.select({
82174
82266
  message: "How should connected terminal sessions start?",
@@ -82191,27 +82283,32 @@ var configureShortcut = fn2("Setup.configureShortcut")(function* (options) {
82191
82283
  }
82192
82284
  return none2();
82193
82285
  });
82286
+ var mobileSetupChoices = (options) => [
82287
+ { title: "Agentstuff is already installed", value: "installed", description: "Open the app and continue to secure pairing" },
82288
+ ...options.developmentEnabled && options.hasProject ? [{
82289
+ title: "Build the development app",
82290
+ value: "development",
82291
+ description: "Developer mode \xB7 uses Xcode and a connected iPhone"
82292
+ }] : [],
82293
+ {
82294
+ title: "Get Agentstuff from the App Store",
82295
+ value: "download",
82296
+ description: options.installUrl === undefined ? "The public iPhone release is not available yet" : "Open the App Store installation page",
82297
+ disabled: options.installUrl === undefined
82298
+ },
82299
+ { title: "Set up the phone later", value: "skip", description: "Continue with desktop setup" }
82300
+ ];
82194
82301
  var prepareMobileApp = fn2("Setup.prepareMobileApp")(function* (yes) {
82195
82302
  const project = findMobileProject();
82196
82303
  const installUrl = getOrUndefined(decodeMobileInstallUrl(process.env.AGENTSTUFF_MOBILE_INSTALL_URL));
82304
+ const developmentEnabled = isSome2(decodeDeveloperSetup(process.env.AGENTSTUFF_DEVELOPER_SETUP));
82197
82305
  const choice5 = yes ? "installed" : yield* exports_Prompt.select({
82198
- message: "Is Agentstuff installed on your iPhone?",
82199
- choices: [
82200
- { title: "Yes, open it now", value: "installed", description: "Continue to secure pairing" },
82201
- {
82202
- title: "Build and install from this checkout",
82203
- value: "development",
82204
- description: project === undefined ? "Run setup from the project checkout to enable this" : "Uses Xcode and a connected iPhone",
82205
- disabled: project === undefined
82206
- },
82207
- {
82208
- title: "Open the mobile download",
82209
- value: "download",
82210
- description: installUrl === undefined ? "No public iOS build has been configured yet" : "Opens the iOS installation page",
82211
- disabled: installUrl === undefined
82212
- },
82213
- { title: "Skip phone pairing for now", value: "skip", description: "Run agentstuff setup again later" }
82214
- ]
82306
+ message: "Set up Agentstuff on your iPhone",
82307
+ choices: mobileSetupChoices({
82308
+ developmentEnabled,
82309
+ hasProject: project !== undefined,
82310
+ installUrl
82311
+ })
82215
82312
  });
82216
82313
  if (choice5 === "skip")
82217
82314
  return false;
@@ -82259,69 +82356,130 @@ var runGuidedSetup = fn2("Setup.run")(function* (options) {
82259
82356
  yield* log("");
82260
82357
  const mode = yield* selectMode(options.yes);
82261
82358
  const sections = setupSections(mode);
82262
- if (sections.desktop && !claudeFound) {
82359
+ if (mode === "repair" && !claudeFound) {
82263
82360
  yield* error("Claude CLI is required for live terminal integration. Install it, then rerun setup.");
82264
82361
  return;
82265
82362
  }
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
82363
  const relayUrl = getOrUndefined(options.relay);
82364
+ const presencePath = join8(options.dataDirectory, "mobile-presence.json");
82365
+ let daemonRunning = daemon.installed;
82366
+ let integrationReady = integrationInstalled;
82367
+ let phoneConnected = yield* loadMobilePresence(presencePath).pipe(catch_2(() => succeed7(false)));
82277
82368
  let configured = none2();
82278
- if (sections.desktop || sections.phone) {
82279
- configured = some2(yield* configurePairing({
82369
+ const ensurePairing = fn2("Setup.ensurePairing")(function* (reset2) {
82370
+ const result3 = yield* configurePairing({
82280
82371
  ...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))) {
82372
+ reset: reset2
82373
+ });
82374
+ if (result3.changed) {
82375
+ phoneConnected = false;
82376
+ yield* saveMobilePresence(false, presencePath);
82377
+ }
82378
+ configured = some2(result3);
82379
+ return result3;
82380
+ });
82381
+ if (sections.phone) {
82299
82382
  yield* log(`
82383
+ Step 1 of 3 \xB7 Connect your iPhone
82384
+ `);
82385
+ if (options.noPair) {
82386
+ yield* log("\xB7 Phone pairing skipped by --no-pair");
82387
+ } else {
82388
+ const hasExistingPairing = isSome2(existing);
82389
+ const pairingAction = options.reset ? "rotate" : options.yes ? hasExistingPairing ? "show" : "create" : hasExistingPairing ? yield* exports_Prompt.select({
82390
+ message: "Mobile pairing is already configured. What should we do?",
82391
+ choices: pairingActions(true).map((action2) => pairingPromptChoice(action2, phoneConnected))
82392
+ }) : "create";
82393
+ if (pairingAction === "keep") {
82394
+ yield* log(`\u2713 Current pairing kept${phoneConnected ? " \xB7 iPhone connected" : ""}`);
82395
+ } else {
82396
+ const pairing = yield* ensurePairing(pairingAction === "rotate");
82397
+ if (pairing.changed && daemonRunning)
82398
+ yield* restartDaemon();
82399
+ if (!daemonRunning) {
82400
+ yield* installDaemon({ dataDirectory: options.dataDirectory, load: true });
82401
+ daemonRunning = true;
82402
+ yield* log("\u2713 Background service started for pairing");
82403
+ }
82404
+ if (yield* prepareMobileApp(options.yes)) {
82405
+ yield* log(`
82300
82406
  On your iPhone, open Agentstuff \u2192 Connect your Mac \u2192 Scan pairing code.
82301
82407
  `);
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.",
82408
+ yield* renderQr(pairingUrl(pairing.pairing));
82409
+ yield* log("Waiting for an authenticated iPhone connection\u2026");
82410
+ phoneConnected = yield* waitForMobilePresence(presencePath, options.yes ? 10 : 60);
82411
+ while (!phoneConnected && !options.yes) {
82412
+ const action2 = yield* exports_Prompt.select({
82413
+ message: "The iPhone has not connected yet.",
82414
+ choices: [
82415
+ { title: "Wait another 30 seconds", value: "retry", description: "Keep the app open after scanning" },
82416
+ { title: "Continue setup and pair later", value: "later", description: "The pairing code remains ready" }
82417
+ ]
82418
+ });
82419
+ if (action2 === "later")
82420
+ break;
82421
+ phoneConnected = yield* waitForMobilePresence(presencePath, 60);
82422
+ }
82423
+ yield* log(phoneConnected ? "\u2713 Authenticated iPhone connected" : "! Pairing is ready. Scan the code from Agentstuff when your phone is available.");
82424
+ }
82425
+ }
82426
+ }
82427
+ }
82428
+ if (sections.desktop) {
82429
+ yield* log(mode === "complete" ? `
82430
+ Step 2 of 3 \xB7 Configure terminal integration
82431
+ ` : `
82432
+ Repairing desktop integration
82433
+ `);
82434
+ if (!claudeFound) {
82435
+ yield* log("! Terminal integration skipped because the required CLI is not installed.");
82436
+ yield* log(" Install it, then choose Repair setup.");
82437
+ } else {
82438
+ const healthy = integrationReady && daemonRunning;
82439
+ const desktopAction = mode === "repair" || options.yes ? "install" : healthy ? yield* exports_Prompt.select({
82440
+ message: "Terminal integration is already running.",
82308
82441
  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" }
82442
+ { title: "Keep the current integration", value: "keep", description: "No changes needed" },
82443
+ { title: "Reinstall the integration", value: "install", description: "Refresh the service and live terminal channel" }
82444
+ ]
82445
+ }) : yield* exports_Prompt.select({
82446
+ message: "Terminal integration needs attention.",
82447
+ choices: [
82448
+ {
82449
+ title: "Configure it now",
82450
+ value: "install",
82451
+ description: integrationReady ? "Restart the background service" : "Install the live terminal channel and background service"
82452
+ },
82453
+ { title: "Skip for now", value: "skip", description: "Finish from Repair setup later" }
82311
82454
  ]
82312
82455
  });
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.");
82456
+ if (desktopAction === "install") {
82457
+ if (isNone2(configured))
82458
+ yield* ensurePairing(options.reset && options.noPair);
82459
+ yield* installTerminalIntegration(undefined, true);
82460
+ integrationReady = true;
82461
+ yield* log("\u2713 Live terminal integration configured");
82462
+ yield* installDaemon({ dataDirectory: options.dataDirectory, load: true });
82463
+ daemonRunning = true;
82464
+ yield* log("\u2713 Background service running");
82465
+ } else if (desktopAction === "keep") {
82466
+ yield* log("\u2713 Current terminal integration kept");
82467
+ } else {
82468
+ yield* log("\xB7 Terminal integration skipped");
82469
+ }
82319
82470
  }
82320
82471
  }
82321
- const launchCommand = getOrElse(shortcut, () => "agentstuff claude");
82472
+ let shortcut = none2();
82473
+ if (sections.shortcut) {
82474
+ yield* log(`
82475
+ Step 3 of 3 \xB7 Configure terminal shortcut
82476
+ `);
82477
+ shortcut = yield* configureShortcut(options, shellCommand);
82478
+ }
82479
+ const launchCommand = getOrElse(shortcut, () => shellCommand ?? "agentstuff claude");
82322
82480
  yield* log(`
82323
82481
  Ready.`);
82324
- if (sections.desktop)
82482
+ if (integrationReady && daemonRunning)
82325
82483
  yield* log(" \u2713 Desktop integration running");
82326
82484
  if (phoneConnected)
82327
82485
  yield* log(" \u2713 iPhone connected");
@@ -85030,5 +85188,5 @@ if (rawCommand === "terminal" || rawCommand === "claude") {
85030
85188
  process.exitCode = exitCode;
85031
85189
  })), asVoid2, provide4(exports_BunServices.layer)));
85032
85190
  } else {
85033
- cli.pipe(exports_Command.run({ version: "0.1.2" }), provide4(exports_BunServices.layer), exports_BunRuntime.runMain);
85191
+ cli.pipe(exports_Command.run({ version: package_default.version }), provide4(exports_BunServices.layer), exports_BunRuntime.runMain);
85034
85192
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentstuff",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",