@wspc/cli 0.1.7 → 0.1.9

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/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { Command as Command81 } from "commander";
4
+ import { Command as Command82 } from "commander";
5
5
  import { realpathSync } from "fs";
6
6
  import { fileURLToPath } from "url";
7
7
 
@@ -1520,9 +1520,9 @@ function createConsistencyFetch(opts) {
1520
1520
  }
1521
1521
 
1522
1522
  // src/version.ts
1523
- var VERSION = "0.1.7";
1523
+ var VERSION = "0.1.9";
1524
1524
  var SPEC_SHA = "bec760cd";
1525
- var SPEC_FETCHED_AT = "2026-06-30T13:42:06.837Z";
1525
+ var SPEC_FETCHED_AT = "2026-07-06T07:30:04.295Z";
1526
1526
  var API_BASE = "https://api.wspc.ai";
1527
1527
 
1528
1528
  // src/index.ts
@@ -4575,8 +4575,27 @@ accountCommand.command("switch <email>").description("Set the active account for
4575
4575
  `);
4576
4576
  });
4577
4577
 
4578
- // src/handwritten/commands/todo-done.ts
4578
+ // src/handwritten/commands/tour.ts
4579
4579
  import { Command as Command75 } from "commander";
4580
+ var GUIDE_URL_DEFAULT = "https://mcp.wspc.ai/guide";
4581
+ function resolveGuideUrl(env = process.env) {
4582
+ const override = env.WSPC_GUIDE_URL?.trim();
4583
+ return override ? override : GUIDE_URL_DEFAULT;
4584
+ }
4585
+ async function fetchGuide(url, fetchImpl = fetch) {
4586
+ const res = await fetchImpl(url);
4587
+ if (!res.ok) throw new Error(`tour_fetch_failed: ${res.status}`);
4588
+ return await res.text();
4589
+ }
4590
+ var TOUR_HINT = "This is the wspc guided tour for your AI agent \u2014 ask your agent to run `wspc tour` and follow it.\n";
4591
+ var tourCommand = new Command75("tour").description("Print the wspc guided-tour script for your AI agent to read and follow").action(async () => {
4592
+ const text = await fetchGuide(resolveGuideUrl());
4593
+ if (process.stdout.isTTY) process.stderr.write(TOUR_HINT);
4594
+ process.stdout.write(text.endsWith("\n") ? text : text + "\n");
4595
+ });
4596
+
4597
+ // src/handwritten/commands/todo-done.ts
4598
+ import { Command as Command76 } from "commander";
4580
4599
  var TODO_UPDATE_DISPLAY = {
4581
4600
  shape: "object",
4582
4601
  format: {
@@ -4594,7 +4613,7 @@ var TODO_UPDATE_DISPLAY = {
4594
4613
  deleted_at: "relative-time"
4595
4614
  }
4596
4615
  };
4597
- var todoDoneCommand = new Command75("done").description("Mark a todo done (sugar for `update <id> --status done`)").argument("<id>", "Todo id").action(async (id) => {
4616
+ var todoDoneCommand = new Command76("done").description("Mark a todo done (sugar for `update <id> --status done`)").argument("<id>", "Todo id").action(async (id) => {
4598
4617
  const client2 = await loadSdkClient();
4599
4618
  const result = await todoUpdate({
4600
4619
  client: client2._rawClient,
@@ -4613,7 +4632,7 @@ var todoDoneCommand = new Command75("done").description("Mark a todo done (sugar
4613
4632
  });
4614
4633
 
4615
4634
  // src/handwritten/commands/email/send.ts
4616
- import { Command as Command76 } from "commander";
4635
+ import { Command as Command77 } from "commander";
4617
4636
  import { randomUUID } from "crypto";
4618
4637
  import { readFile, stat } from "fs/promises";
4619
4638
  import { basename } from "path";
@@ -4672,7 +4691,7 @@ async function resolveAttachment(input) {
4672
4691
  `--attach ${input}: neither a readable file nor a valid <prefix>_<ulid>:<idx> reference.`
4673
4692
  );
4674
4693
  }
4675
- var sendCommand = new Command76("send").description("Send an outbound email").requiredOption("--from <alias-email>", "alias email to send from").option("--to <addr...>", "recipient address (repeatable)", []).option("--subject <text>", "subject").option("--text <body>", "plain-text body").option("--text-file <path>", "read text body from file").option("--reply <id>", "inbound email id to reply to").option("--attach <path-or-ref...>", "attachment (file path or eml_xxx:idx)", []).option("--idempotency-key <key>", "idempotency key (auto-generated if omitted)").action(async (opts) => {
4694
+ var sendCommand = new Command77("send").description("Send an outbound email").requiredOption("--from <alias-email>", "alias email to send from").option("--to <addr...>", "recipient address (repeatable)", []).option("--subject <text>", "subject").option("--text <body>", "plain-text body").option("--text-file <path>", "read text body from file").option("--reply <id>", "inbound email id to reply to").option("--attach <path-or-ref...>", "attachment (file path or eml_xxx:idx)", []).option("--idempotency-key <key>", "idempotency key (auto-generated if omitted)").action(async (opts) => {
4676
4695
  const isReply = Boolean(opts.reply);
4677
4696
  const to = opts.to;
4678
4697
  const attachInputs = opts.attach;
@@ -4761,7 +4780,7 @@ var sendCommand = new Command76("send").description("Send an outbound email").re
4761
4780
  });
4762
4781
 
4763
4782
  // src/handwritten/commands/email/attachment.ts
4764
- import { Command as Command77 } from "commander";
4783
+ import { Command as Command78 } from "commander";
4765
4784
  import { createWriteStream } from "fs";
4766
4785
  import { Readable } from "stream";
4767
4786
  import { pipeline } from "stream/promises";
@@ -4779,7 +4798,7 @@ function parseContentDispositionFilename(header) {
4779
4798
  }
4780
4799
 
4781
4800
  // src/handwritten/commands/email/attachment.ts
4782
- var attachmentCommand = new Command77("attachment").description("Download an inbound email attachment by index").argument("<email-id>").argument("<idx>").option("--output <path>", "output file path").option("--include-deleted", "allow downloads from soft-deleted parent emails").action(async (emailId, idxArg, opts) => {
4801
+ var attachmentCommand = new Command78("attachment").description("Download an inbound email attachment by index").argument("<email-id>").argument("<idx>").option("--output <path>", "output file path").option("--include-deleted", "allow downloads from soft-deleted parent emails").action(async (emailId, idxArg, opts) => {
4783
4802
  const idx = Number(idxArg);
4784
4803
  if (!Number.isInteger(idx) || idx < 0) {
4785
4804
  process.stderr.write(`<idx> must be a non-negative integer (got "${idxArg}")
@@ -4811,7 +4830,7 @@ var attachmentCommand = new Command77("attachment").description("Download an inb
4811
4830
  });
4812
4831
 
4813
4832
  // src/handwritten/commands/drive/bind.ts
4814
- import { Command as Command78 } from "commander";
4833
+ import { Command as Command79 } from "commander";
4815
4834
  import { stat as stat3 } from "fs/promises";
4816
4835
  import { resolve } from "path";
4817
4836
 
@@ -5086,7 +5105,7 @@ async function assertExistingDirectory(path) {
5086
5105
  }
5087
5106
  }
5088
5107
  function driveBindCommand() {
5089
- return new Command78("bind").description("Bind a local folder to an existing Drive library").requiredOption("--library <id>", "existing Drive library id").argument("[path]", "local folder path", ".").action(async (path, opts) => {
5108
+ return new Command79("bind").description("Bind a local folder to an existing Drive library").requiredOption("--library <id>", "existing Drive library id").argument("[path]", "local folder path", ".").action(async (path, opts) => {
5090
5109
  const root = resolve(path);
5091
5110
  await assertExistingDirectory(root);
5092
5111
  const api = await createDriveApi();
@@ -5104,7 +5123,7 @@ function driveBindCommand() {
5104
5123
  }
5105
5124
 
5106
5125
  // src/handwritten/commands/drive/sync.ts
5107
- import { Command as Command79 } from "commander";
5126
+ import { Command as Command80 } from "commander";
5108
5127
  import { mkdir as mkdir3, readFile as readFile4, rm as rm3, writeFile as writeFile3 } from "fs/promises";
5109
5128
  import { basename as basename3, dirname as dirname2, join as join5, posix as pathPosix2, resolve as resolve3 } from "path";
5110
5129
  import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
@@ -5821,7 +5840,7 @@ async function runDriveSyncOnce(root, api, clock = systemDriveClock) {
5821
5840
  });
5822
5841
  }
5823
5842
  function driveSyncCommand(api) {
5824
- const sync = new Command79("sync").description("Drive sync commands");
5843
+ const sync = new Command80("sync").description("Drive sync commands");
5825
5844
  sync.command("once").description("Run one Drive sync pass").argument("[path]", "local folder path", ".").action(async (path) => {
5826
5845
  const summary = await runDriveSyncOnce(resolve3(path), api);
5827
5846
  render({ kind: "drive_sync_once", display: { shape: "object" } }, summary);
@@ -6274,7 +6293,7 @@ function containsVersionConflict(value) {
6274
6293
  }
6275
6294
 
6276
6295
  // src/handwritten/commands/drive/watch.ts
6277
- import { Command as Command80 } from "commander";
6296
+ import { Command as Command81 } from "commander";
6278
6297
  import chokidar from "chokidar";
6279
6298
  import { relative as relative2, resolve as resolve4 } from "path";
6280
6299
 
@@ -6312,6 +6331,15 @@ function createDriveRealtimeSource(args) {
6312
6331
  if (stopped || authFailed) return;
6313
6332
  clearReconnectTimer();
6314
6333
  const id = ++connectionId;
6334
+ const headers = args.headers;
6335
+ if (typeof headers !== "function") {
6336
+ openSocket(id, headers);
6337
+ return;
6338
+ }
6339
+ void headers().then((resolved) => openSocket(id, resolved)).catch((error) => closeConnection(id, error));
6340
+ }
6341
+ function openSocket(id, headers) {
6342
+ if (id !== connectionId || stopped || authFailed) return;
6315
6343
  const url = buildDriveRealtimeUrl(args.baseUrl, args.libraryId, currentRealtime);
6316
6344
  activeSocket = connect(url, {
6317
6345
  open() {
@@ -6326,7 +6354,7 @@ function createDriveRealtimeSource(args) {
6326
6354
  close(error) {
6327
6355
  closeConnection(id, error ?? "close");
6328
6356
  }
6329
- }, args.headers === void 0 ? void 0 : { headers: args.headers });
6357
+ }, headers === void 0 ? void 0 : { headers });
6330
6358
  }
6331
6359
  function closeConnection(id, error) {
6332
6360
  if (id !== connectionId || stopped || authFailed) return;
@@ -6521,6 +6549,9 @@ function isInvalidCursorReason(reason) {
6521
6549
  return /\bcursor[_ -]?(invalid|expired|gone|missing|not[_ -]?found)\b|\binvalid[_ -]?cursor\b/i.test(reason);
6522
6550
  }
6523
6551
  function isRealtimeAuthError(error) {
6552
+ if (typeof error === "object" && error !== null && error.code === "WSPC_AUTH_EXPIRED") {
6553
+ return true;
6554
+ }
6524
6555
  return /\b(401|403|auth|authorization|unauthorized|forbidden)\b/i.test(String(error));
6525
6556
  }
6526
6557
  function nativeWebSocketConnector(url, handlers, init) {
@@ -6664,16 +6695,17 @@ async function runDriveWatch(root, options = {}) {
6664
6695
  realtimeSource = options.once ? void 0 : options.realtimeSource;
6665
6696
  if (!options.once && realtimeSource === void 0) {
6666
6697
  state = await ensureDriveRealtimeState(root);
6667
- const { baseUrl, headers } = await loadRealtimeAuthHeaders({
6668
- verifyPath: `/drive/libraries/${encodeURIComponent(state.library_id)}`
6669
- });
6698
+ const verifyPath = `/drive/libraries/${encodeURIComponent(state.library_id)}`;
6699
+ const { baseUrl } = await loadRealtimeAuthHeaders({ verifyPath });
6670
6700
  const realtime = state.realtime;
6671
6701
  if (realtime === void 0) {
6672
6702
  throw new Error("drive realtime state is required");
6673
6703
  }
6674
6704
  realtimeSource = createDriveRealtimeSource({
6675
6705
  baseUrl,
6676
- headers,
6706
+ // Tokens expire while watching; resolve fresh headers on every
6707
+ // (re)connect so an idle drop cannot loop on a stale snapshot.
6708
+ headers: async () => (await loadRealtimeAuthHeaders({ verifyPath })).headers,
6677
6709
  libraryId: state.library_id,
6678
6710
  realtime,
6679
6711
  writeRealtimeState: (next) => writeDriveRealtimeState(root, next)
@@ -6724,7 +6756,7 @@ async function runDriveWatch(root, options = {}) {
6724
6756
  }
6725
6757
  }
6726
6758
  function driveWatchCommand(options = {}) {
6727
- return new Command80("watch").description("Watch a bound Drive folder and sync local changes").argument("[path]", "local folder path", ".").action(async (path) => {
6759
+ return new Command81("watch").description("Watch a bound Drive folder and sync local changes").argument("[path]", "local folder path", ".").action(async (path) => {
6728
6760
  await runDriveWatch(resolve4(path), options);
6729
6761
  });
6730
6762
  }
@@ -6788,7 +6820,7 @@ function waitForStopSignal(onRegistered) {
6788
6820
  function mountDriveCommands(program) {
6789
6821
  let drive = program.commands.find((c) => c.name() === "drive");
6790
6822
  if (!drive) {
6791
- drive = new Command81("drive").description("Drive commands");
6823
+ drive = new Command82("drive").description("Drive commands");
6792
6824
  program.addCommand(drive);
6793
6825
  }
6794
6826
  if (!drive.commands.some((c) => c.name() === "bind")) {
@@ -6814,7 +6846,7 @@ function isCliEntrypoint(argv = process.argv, metaUrl = import.meta.url) {
6814
6846
  }
6815
6847
  }
6816
6848
  function buildProgram() {
6817
- const program = new Command81().name("wspc").description("Official CLI for wspc.ai").version(`wspc ${VERSION} (spec ${SPEC_SHA}, fetched ${SPEC_FETCHED_AT})`).option("--json", "Output raw JSON (machine-readable)").option("--account <email>", "Run as a specific account (overrides the active account)").hook("preAction", (_thisCommand, actionCommand) => {
6849
+ const program = new Command82().name("wspc").description("Official CLI for wspc.ai").version(`wspc ${VERSION} (spec ${SPEC_SHA}, fetched ${SPEC_FETCHED_AT})`).option("--json", "Output raw JSON (machine-readable)").option("--account <email>", "Run as a specific account (overrides the active account)").hook("preAction", (_thisCommand, actionCommand) => {
6818
6850
  const globals = actionCommand.optsWithGlobals();
6819
6851
  if (globals.json) process.env.WSPC_OUTPUT = "json";
6820
6852
  if (globals.account) process.env.WSPC_ACCOUNT = String(globals.account);
@@ -6824,6 +6856,7 @@ function buildProgram() {
6824
6856
  program.addCommand(whoamiCommand);
6825
6857
  program.addCommand(configCommand);
6826
6858
  program.addCommand(accountCommand);
6859
+ program.addCommand(tourCommand);
6827
6860
  registerGeneratedCommands(program);
6828
6861
  mountDriveCommands(program);
6829
6862
  const todo = program.commands.find((c) => c.name() === "todo");