itsvertical 0.0.5 → 0.0.7

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 CHANGED
@@ -51,6 +51,7 @@ All entities are addressed by ID. Use `itsvertical show` to see IDs. Every comma
51
51
  ### Project
52
52
 
53
53
  ```
54
+ itsvertical <file> # Shorthand for "open"
54
55
  itsvertical new <path> <name> # Create a new .vertical file
55
56
  itsvertical open <file> # Open in the browser UI
56
57
  itsvertical show <file> # Print the board to the terminal
@@ -96,7 +97,7 @@ itsvertical layer status <file> <layer-id> none # Clear status
96
97
 
97
98
  ### Browser UI
98
99
 
99
- `itsvertical open` starts a local server and opens the board in your browser. Click **Save** or press **Ctrl+S** / **Cmd+S** to write changes back to the file.
100
+ `itsvertical open` (or just `itsvertical <file>`) starts a local server and opens the board in your browser. Changes are saved automatically.
100
101
 
101
102
  ## The board
102
103
 
@@ -121,7 +122,6 @@ Each box represents a vertical slice of work.
121
122
  | **Delete** / **Backspace** | Delete focused task or unsplit focused layer |
122
123
  | **Enter** | Save inline edit |
123
124
  | **Shift+Enter** | Save edit and create a new task below |
124
- | **Ctrl/Cmd+S** | Save to file |
125
125
 
126
126
  ## The `.vertical` file
127
127
 
package/cli/dist/index.js CHANGED
@@ -1472,8 +1472,8 @@ var require_react = __commonJS({
1472
1472
  });
1473
1473
 
1474
1474
  // cli/index.ts
1475
- import fs3 from "fs";
1476
- import path3 from "path";
1475
+ import fs4 from "fs";
1476
+ import path4 from "path";
1477
1477
  import { dirname } from "path";
1478
1478
  import { fileURLToPath as fileURLToPath2 } from "url";
1479
1479
  import { Command } from "commander";
@@ -2255,6 +2255,283 @@ async function startServer(filePath, options = {}) {
2255
2255
  });
2256
2256
  }
2257
2257
 
2258
+ // cli/update.ts
2259
+ import { execFile } from "child_process";
2260
+ import fs3 from "fs";
2261
+ import os from "os";
2262
+ import path3 from "path";
2263
+ var CACHE_DIR = path3.join(os.homedir(), ".itsvertical");
2264
+ var CACHE_FILE = path3.join(CACHE_DIR, "update-check.json");
2265
+ var CHECK_INTERVAL = 36e5;
2266
+ var FETCH_TIMEOUT = 5e3;
2267
+ var REGISTRY_URL = "https://registry.npmjs.org/itsvertical/latest";
2268
+ function compareSemver(a, b) {
2269
+ const pa = a.split(".").map(Number);
2270
+ const pb = b.split(".").map(Number);
2271
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
2272
+ const na = pa[i] ?? 0;
2273
+ const nb = pb[i] ?? 0;
2274
+ if (na > nb)
2275
+ return 1;
2276
+ if (na < nb)
2277
+ return -1;
2278
+ }
2279
+ return 0;
2280
+ }
2281
+ function isPathInside(childPath, parentPath) {
2282
+ const relative = path3.relative(parentPath, childPath);
2283
+ return relative !== "" && !relative.startsWith("..") && !path3.isAbsolute(relative);
2284
+ }
2285
+ function resolveScriptPath(scriptPath) {
2286
+ try {
2287
+ return fs3.realpathSync(scriptPath);
2288
+ } catch {
2289
+ return scriptPath;
2290
+ }
2291
+ }
2292
+ function getNpmGlobalPackages() {
2293
+ const isWindows = process.platform === "win32";
2294
+ const envKey = Object.keys(process.env).find(
2295
+ (k) => k.toLowerCase() === "npm_config_prefix"
2296
+ );
2297
+ const envValue = envKey ? process.env[envKey] : void 0;
2298
+ if (envValue) {
2299
+ const prefix2 = path3.resolve(envValue);
2300
+ return path3.join(prefix2, isWindows ? "node_modules" : "lib/node_modules");
2301
+ }
2302
+ let prefix;
2303
+ if (isWindows) {
2304
+ prefix = process.env.APPDATA ? path3.join(process.env.APPDATA, "npm") : path3.dirname(process.execPath);
2305
+ } else if (process.execPath.includes("/Cellar/node")) {
2306
+ prefix = process.execPath.slice(0, process.execPath.indexOf("/Cellar/node"));
2307
+ } else {
2308
+ prefix = path3.dirname(path3.dirname(process.execPath));
2309
+ }
2310
+ return path3.join(prefix, isWindows ? "node_modules" : "lib/node_modules");
2311
+ }
2312
+ function getPnpmGlobalDir() {
2313
+ if (process.env.PNPM_HOME)
2314
+ return process.env.PNPM_HOME;
2315
+ if (process.env.XDG_DATA_HOME)
2316
+ return path3.join(process.env.XDG_DATA_HOME, "pnpm");
2317
+ if (process.platform === "darwin")
2318
+ return path3.join(os.homedir(), "Library/pnpm");
2319
+ if (process.platform !== "win32")
2320
+ return path3.join(os.homedir(), ".local/share/pnpm");
2321
+ if (process.env.LOCALAPPDATA)
2322
+ return path3.join(process.env.LOCALAPPDATA, "pnpm");
2323
+ return path3.join(os.homedir(), ".pnpm");
2324
+ }
2325
+ function getYarnGlobalPackages() {
2326
+ const isWindows = process.platform === "win32";
2327
+ let dataDir;
2328
+ if (isWindows) {
2329
+ dataDir = process.env.LOCALAPPDATA ? path3.join(process.env.LOCALAPPDATA, "Yarn/Data") : path3.join(os.homedir(), ".config/yarn");
2330
+ } else if (process.env.XDG_DATA_HOME) {
2331
+ dataDir = path3.join(process.env.XDG_DATA_HOME, "yarn");
2332
+ } else {
2333
+ dataDir = path3.join(os.homedir(), ".config/yarn");
2334
+ }
2335
+ return path3.join(dataDir, "global/node_modules");
2336
+ }
2337
+ function getBunGlobalDir() {
2338
+ return process.env.BUN_INSTALL || path3.join(os.homedir(), ".bun");
2339
+ }
2340
+ function detectInstallContext(scriptPath) {
2341
+ const resolved = resolveScriptPath(scriptPath);
2342
+ const npxMatch = resolved.match(/(.+\/_npx\/[^/]+)/);
2343
+ if (npxMatch) {
2344
+ return { type: "npx-cache", cachePath: npxMatch[1] };
2345
+ }
2346
+ const bunDir = path3.resolve(getBunGlobalDir());
2347
+ if (isPathInside(resolved, bunDir)) {
2348
+ const bunCacheDir = path3.join(bunDir, "install", "cache");
2349
+ if (isPathInside(resolved, bunCacheDir)) {
2350
+ return { type: "bunx-cache", cacheDir: bunCacheDir };
2351
+ }
2352
+ return { type: "global", packageManager: "bun" };
2353
+ }
2354
+ const pnpmDir = path3.resolve(getPnpmGlobalDir());
2355
+ if (isPathInside(resolved, pnpmDir)) {
2356
+ return { type: "global", packageManager: "pnpm" };
2357
+ }
2358
+ const yarnPackages = path3.resolve(getYarnGlobalPackages());
2359
+ if (isPathInside(resolved, yarnPackages)) {
2360
+ return { type: "global", packageManager: "yarn" };
2361
+ }
2362
+ try {
2363
+ const npmPackages = fs3.realpathSync(getNpmGlobalPackages());
2364
+ if (isPathInside(resolved, npmPackages)) {
2365
+ return { type: "global", packageManager: "npm" };
2366
+ }
2367
+ } catch {
2368
+ }
2369
+ if (resolved.includes("/node_modules/itsvertical/") || resolved.includes("\\node_modules\\itsvertical\\")) {
2370
+ return { type: "local" };
2371
+ }
2372
+ return { type: "unknown" };
2373
+ }
2374
+ function readCache() {
2375
+ try {
2376
+ return JSON.parse(fs3.readFileSync(CACHE_FILE, "utf-8"));
2377
+ } catch {
2378
+ return null;
2379
+ }
2380
+ }
2381
+ function writeCache(cache) {
2382
+ try {
2383
+ fs3.mkdirSync(CACHE_DIR, { recursive: true });
2384
+ fs3.writeFileSync(CACHE_FILE, JSON.stringify(cache));
2385
+ } catch {
2386
+ }
2387
+ }
2388
+ function shouldCheckForUpdate(currentVersion) {
2389
+ if (process.env.NO_UPDATE_CHECK)
2390
+ return false;
2391
+ const cache = readCache();
2392
+ if (!cache)
2393
+ return true;
2394
+ const scriptPath = resolveScriptPath(process.argv[1]);
2395
+ if (cache.scriptPath !== scriptPath)
2396
+ return true;
2397
+ if (cache.currentVersion !== currentVersion)
2398
+ return true;
2399
+ if (Date.now() - cache.lastCheck >= CHECK_INTERVAL)
2400
+ return true;
2401
+ return false;
2402
+ }
2403
+ async function fetchLatestVersion() {
2404
+ try {
2405
+ const controller = new AbortController();
2406
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
2407
+ const response = await fetch(REGISTRY_URL, { signal: controller.signal });
2408
+ clearTimeout(timeout);
2409
+ if (!response.ok)
2410
+ return null;
2411
+ const data = await response.json();
2412
+ return data.version ?? null;
2413
+ } catch {
2414
+ return null;
2415
+ }
2416
+ }
2417
+ async function checkForUpdate(currentVersion, { skipCache = false } = {}) {
2418
+ if (!skipCache && !shouldCheckForUpdate(currentVersion)) {
2419
+ const cache = readCache();
2420
+ if (cache && compareSemver(cache.latestVersion, currentVersion) > 0) {
2421
+ return {
2422
+ latestVersion: cache.latestVersion,
2423
+ installContext: cache.installContext
2424
+ };
2425
+ }
2426
+ return null;
2427
+ }
2428
+ const latestVersion = await fetchLatestVersion();
2429
+ if (!latestVersion)
2430
+ return null;
2431
+ const scriptPath = resolveScriptPath(process.argv[1]);
2432
+ const installContext = detectInstallContext(scriptPath);
2433
+ writeCache({
2434
+ lastCheck: Date.now(),
2435
+ latestVersion,
2436
+ currentVersion,
2437
+ installContext,
2438
+ scriptPath
2439
+ });
2440
+ if (compareSemver(latestVersion, currentVersion) > 0) {
2441
+ return { latestVersion, installContext };
2442
+ }
2443
+ return null;
2444
+ }
2445
+ function getUpdateCommand(context) {
2446
+ if (context.type === "local")
2447
+ return null;
2448
+ if (context.type === "npx-cache")
2449
+ return null;
2450
+ if (context.type === "bunx-cache")
2451
+ return null;
2452
+ const pm = context.type === "global" ? context.packageManager : "npm";
2453
+ switch (pm) {
2454
+ case "npm":
2455
+ return ["npm", "install", "-g", "itsvertical@latest"];
2456
+ case "pnpm":
2457
+ return ["pnpm", "add", "-g", "itsvertical@latest"];
2458
+ case "yarn":
2459
+ return ["yarn", "global", "add", "itsvertical@latest"];
2460
+ case "bun":
2461
+ return ["bun", "install", "-g", "itsvertical@latest"];
2462
+ }
2463
+ }
2464
+ function getUpdateCommandString(context) {
2465
+ const cmd = getUpdateCommand(context);
2466
+ if (!cmd)
2467
+ return "npm install -g itsvertical@latest";
2468
+ return cmd.join(" ");
2469
+ }
2470
+ async function performUpdate(context) {
2471
+ if (context.type === "npx-cache") {
2472
+ try {
2473
+ fs3.rmSync(context.cachePath, { recursive: true, force: true });
2474
+ return {
2475
+ success: true,
2476
+ message: "Cleared npx cache. Next run will fetch the latest version."
2477
+ };
2478
+ } catch {
2479
+ return { success: false, message: "Failed to clear npx cache." };
2480
+ }
2481
+ }
2482
+ if (context.type === "bunx-cache") {
2483
+ try {
2484
+ for (const entry of fs3.readdirSync(context.cacheDir)) {
2485
+ if (entry.startsWith("itsvertical@")) {
2486
+ fs3.rmSync(path3.join(context.cacheDir, entry), {
2487
+ recursive: true,
2488
+ force: true
2489
+ });
2490
+ }
2491
+ }
2492
+ return {
2493
+ success: true,
2494
+ message: "Cleared bunx cache. Next run will fetch the latest version."
2495
+ };
2496
+ } catch {
2497
+ return { success: false, message: "Failed to clear bunx cache." };
2498
+ }
2499
+ }
2500
+ if (context.type === "local") {
2501
+ return {
2502
+ success: false,
2503
+ message: "Cannot auto-update a local dependency. Update your package.json instead."
2504
+ };
2505
+ }
2506
+ const cmd = getUpdateCommand(context);
2507
+ if (!cmd) {
2508
+ return { success: false, message: "Could not determine update command." };
2509
+ }
2510
+ return new Promise((resolve) => {
2511
+ execFile(cmd[0], cmd.slice(1), { timeout: 6e4 }, (error) => {
2512
+ if (error) {
2513
+ resolve({
2514
+ success: false,
2515
+ message: `Update failed. Run manually: ${cmd.join(" ")}`
2516
+ });
2517
+ } else {
2518
+ resolve({ success: true, message: "Updated successfully." });
2519
+ }
2520
+ });
2521
+ });
2522
+ }
2523
+ async function checkAndUpdate(currentVersion) {
2524
+ try {
2525
+ const result = await checkForUpdate(currentVersion);
2526
+ if (!result)
2527
+ return;
2528
+ if (result.installContext.type === "local")
2529
+ return;
2530
+ await performUpdate(result.installContext);
2531
+ } catch {
2532
+ }
2533
+ }
2534
+
2258
2535
  // cli/index.ts
2259
2536
  function getDirname() {
2260
2537
  if (typeof __dirname !== "undefined")
@@ -2265,26 +2542,26 @@ function getDirname() {
2265
2542
  throw new Error("Cannot determine directory path in current environment");
2266
2543
  }
2267
2544
  var packageJson = JSON.parse(
2268
- fs3.readFileSync(
2269
- path3.resolve(getDirname(), "..", "..", "package.json"),
2545
+ fs4.readFileSync(
2546
+ path4.resolve(getDirname(), "..", "..", "package.json"),
2270
2547
  "utf8"
2271
2548
  )
2272
2549
  );
2273
2550
  var program = new Command();
2274
2551
  program.name("itsvertical").description(
2275
- "Tickets pile up, scopes get done. Project work isn't linear, it's Vertical."
2552
+ "Tickets pile up, scopes get done. Project work isn't linear, it's Vertical.\n\nTip: itsvertical <file.vertical> is a shorthand for itsvertical open <file>."
2276
2553
  ).version(packageJson.version);
2277
2554
  program.command("new").description("Create a new .vertical project file").argument("<path>", "File path for the new .vertical file").argument("<name>", "Project name").option("--json", "Output as JSON").action((fileDest, name, options) => {
2278
- const filePath = path3.resolve(fileDest);
2279
- if (fs3.existsSync(filePath)) {
2555
+ const filePath = path4.resolve(fileDest);
2556
+ if (fs4.existsSync(filePath)) {
2280
2557
  fail(`File already exists: ${filePath}`, options.json);
2281
2558
  }
2282
- const dir = path3.dirname(filePath);
2283
- if (!fs3.existsSync(dir)) {
2284
- fs3.mkdirSync(dir, { recursive: true });
2559
+ const dir = path4.dirname(filePath);
2560
+ if (!fs4.existsSync(dir)) {
2561
+ fs4.mkdirSync(dir, { recursive: true });
2285
2562
  }
2286
2563
  const state = createBlankProject(name);
2287
- fs3.writeFileSync(filePath, serialize(state));
2564
+ fs4.writeFileSync(filePath, serialize(state));
2288
2565
  output(state, Boolean(options.json), `Created: ${filePath}`);
2289
2566
  });
2290
2567
  program.command("open").description("Open an existing .vertical file in the browser").argument("<file>", "Path to the .vertical file").action(async (file) => {
@@ -2553,10 +2830,78 @@ layer.command("status").description("Set the status of a layer").argument("<file
2553
2830
  );
2554
2831
  }
2555
2832
  );
2833
+ program.command("update").description("Check for and install updates").option("--json", "Output as JSON").option("--check", "Only check, do not install").action(async (options) => {
2834
+ const result = await checkForUpdate(packageJson.version, {
2835
+ skipCache: true
2836
+ });
2837
+ if (!result) {
2838
+ if (options.json) {
2839
+ console.log(
2840
+ JSON.stringify({
2841
+ currentVersion: packageJson.version,
2842
+ latestVersion: packageJson.version,
2843
+ updated: false,
2844
+ message: "Already up to date"
2845
+ })
2846
+ );
2847
+ } else {
2848
+ console.log(`Already up to date (v${packageJson.version})`);
2849
+ }
2850
+ return;
2851
+ }
2852
+ if (options.check) {
2853
+ if (options.json) {
2854
+ console.log(
2855
+ JSON.stringify({
2856
+ currentVersion: packageJson.version,
2857
+ latestVersion: result.latestVersion,
2858
+ updated: false,
2859
+ updateCommand: getUpdateCommandString(result.installContext),
2860
+ message: `Update available: v${result.latestVersion}`
2861
+ })
2862
+ );
2863
+ } else {
2864
+ console.log(
2865
+ `Update available: v${packageJson.version} \u2192 v${result.latestVersion}`
2866
+ );
2867
+ console.log(`Run: ${getUpdateCommandString(result.installContext)}`);
2868
+ }
2869
+ return;
2870
+ }
2871
+ const { success, message } = await performUpdate(result.installContext);
2872
+ if (options.json) {
2873
+ console.log(
2874
+ JSON.stringify({
2875
+ currentVersion: packageJson.version,
2876
+ latestVersion: result.latestVersion,
2877
+ updated: success,
2878
+ message: success ? `Updated to v${result.latestVersion}` : message
2879
+ })
2880
+ );
2881
+ } else {
2882
+ if (success) {
2883
+ console.log(
2884
+ `Updated: v${packageJson.version} \u2192 v${result.latestVersion}`
2885
+ );
2886
+ } else {
2887
+ console.error(`Error: ${message}`);
2888
+ }
2889
+ }
2890
+ if (!success)
2891
+ process.exit(1);
2892
+ });
2556
2893
  if (process.argv.length === 2) {
2557
2894
  program.outputHelp();
2558
2895
  } else {
2559
- program.parse(process.argv);
2896
+ const args = process.argv.slice(2);
2897
+ if (args.length === 1 && args[0].endsWith(".vertical")) {
2898
+ program.parse([...process.argv.slice(0, 2), "open", args[0]]);
2899
+ } else {
2900
+ program.parse(process.argv);
2901
+ }
2902
+ if (args[0] !== "update") {
2903
+ checkAndUpdate(packageJson.version);
2904
+ }
2560
2905
  }
2561
2906
  /*! Bundled license information:
2562
2907
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itsvertical",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "description": "Tickets pile up, scopes get done. Project work isn't linear, it's Vertical.",
6
6
  "license": "MIT",