@lumoai/cli 1.0.0 → 1.1.0

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
@@ -14,6 +14,14 @@ Verify:
14
14
  lumo --version
15
15
  ```
16
16
 
17
+ ## Update
18
+
19
+ ```bash
20
+ lumo update
21
+ ```
22
+
23
+ Checks npm for a newer release and runs `npm install -g @lumoai/cli@latest` if one is available. The CLI also performs a background check once every 24 hours and prints a one-line notice on subsequent invocations when an upgrade is available.
24
+
17
25
  ## Authentication
18
26
 
19
27
  ```bash
@@ -60,6 +68,7 @@ lumo task update LUM-42 --status done
60
68
  | --------------- | ------------------------------------------------------------- |
61
69
  | `auth` | `login`, `logout` |
62
70
  | `whoami` | Show current identity + workspace |
71
+ | `update` | Self-update to the latest npm release |
63
72
  | `task` | `create`, `update`, `list`, `show`, `comment`, `context` |
64
73
  | `session` | `attach`, `status`, `detach` (binds Claude Code sessions) |
65
74
  | `project` | `list` |
@@ -74,13 +83,6 @@ lumo task create --help
74
83
  lumo sprint close --help
75
84
  ```
76
85
 
77
- ## Environment Variables
78
-
79
- | Variable | Default | Purpose |
80
- | ------------------------- | ------------------------ | ------------------------------------------------------------- |
81
- | `LUMO_API_URL` | `https://www.uselumo.ai` | Override the API endpoint (e.g. self-hosted Lumo instance). |
82
- | `CLAUDE_CODE_SESSION_ID` | _(set by Claude Code)_ | Required for `session attach` / `status` / `detach`. |
83
-
84
86
  ## Claude Code Integration
85
87
 
86
88
  Inside a Claude Code session, the agent skill auto-discovers the CLI and uses it to load task context, bind sessions, and update task state. See the [Lumo Claude Code skill](https://github.com/Lumo-Workspace/lumo/tree/main/.claude/skills/lumo) for the full agent contract.
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.update = update;
37
+ const child_process_1 = require("child_process");
38
+ const path = __importStar(require("path"));
39
+ const update_check_1 = require("../lib/update-check");
40
+ async function update() {
41
+ const pkg = require(path.resolve(__dirname, '../../../..', 'package.json'));
42
+ process.stdout.write(`Current: ${pkg.name}@${pkg.version}\n`);
43
+ process.stdout.write('Checking npm registry...\n');
44
+ const latest = await (0, update_check_1.fetchLatestVersion)(pkg.name, 5000);
45
+ if (!latest) {
46
+ process.stderr.write('Could not reach npm registry. Check your connection and try again.\n');
47
+ return 1;
48
+ }
49
+ if (latest === pkg.version) {
50
+ process.stdout.write(`Already on the latest version (${latest}).\n`);
51
+ return 0;
52
+ }
53
+ if (!(0, update_check_1.isNewer)(pkg.version, latest)) {
54
+ process.stdout.write(`Installed version (${pkg.version}) is ahead of the published latest (${latest}). Nothing to do.\n`);
55
+ return 0;
56
+ }
57
+ process.stdout.write(`Latest: ${pkg.name}@${latest}\n\n`);
58
+ process.stdout.write(`Running: npm install -g ${pkg.name}@latest\n\n`);
59
+ return new Promise(resolve => {
60
+ const proc = (0, child_process_1.spawn)('npm', ['install', '-g', `${pkg.name}@latest`], {
61
+ stdio: 'inherit',
62
+ shell: process.platform === 'win32',
63
+ });
64
+ proc.on('close', code => resolve(code ?? 0));
65
+ proc.on('error', err => {
66
+ process.stderr.write(`Failed to launch npm: ${err.message}\n`);
67
+ process.stderr.write(`Fallback: run \`npm install -g ${pkg.name}@latest\` manually.\n`);
68
+ resolve(1);
69
+ });
70
+ });
71
+ }
@@ -78,9 +78,29 @@ const doc_share_1 = require("./commands/doc-share");
78
78
  const doc_unshare_1 = require("./commands/doc-unshare");
79
79
  const doc_share_list_1 = require("./commands/doc-share-list");
80
80
  const doc_move_1 = require("./commands/doc-move");
81
+ const update_1 = require("./commands/update");
82
+ const update_check_1 = require("./lib/update-check");
81
83
  // Resolve package.json relative to __dirname so this works regardless of how
82
84
  // deep the compiled output ends up (flat dist/ or nested dist/cli/src/).
83
85
  const pkg = require(path.resolve(__dirname, '../../..', 'package.json'));
86
+ // Detached background-refresh worker: re-entry point for the spawn() in
87
+ // maybeRefreshInBackground(). Fetches latest, writes cache, exits — skipping
88
+ // commander.parseAsync() below.
89
+ const isUpdateCheckWorker = process.argv[2] === '__update-check' && !!process.argv[3];
90
+ if (isUpdateCheckWorker) {
91
+ (0, update_check_1.runBackgroundRefresh)(process.argv[3])
92
+ .catch(() => { })
93
+ .finally(() => process.exit(0));
94
+ }
95
+ else {
96
+ (0, update_check_1.printUpdateNoticeIfAny)(pkg.name, pkg.version);
97
+ (0, update_check_1.maybeRefreshInBackground)(pkg.name);
98
+ }
99
+ // One-shot cleanup of pre-LUM-46 local state. The session→task binding
100
+ // is now server-authoritative; the legacy global pointer and the
101
+ // per-session sentinel directory are both obsolete. Failures are
102
+ // swallowed so a permission glitch doesn't prevent CLI invocation.
103
+ ;
84
104
  (() => {
85
105
  const dir = process.env.LUMO_CONFIG_DIR || path.join(os.homedir(), '.lumo');
86
106
  try {
@@ -124,6 +144,10 @@ program
124
144
  .command('whoami')
125
145
  .description('Show the currently logged-in identity')
126
146
  .action(wrap(whoami_1.whoami));
147
+ program
148
+ .command('update')
149
+ .description(`Check npm for a newer ${pkg.name} and install it globally if available.`)
150
+ .action(wrap(update_1.update));
127
151
  const session = program
128
152
  .command('session')
129
153
  .description('Manage per-terminal coding-session context');
@@ -485,8 +509,10 @@ hook
485
509
  .command('instructions-loaded')
486
510
  .description('Forward an InstructionsLoaded hook event to Lumo (reads JSON from stdin)')
487
511
  .action(wrap(() => (0, hook_1.hookCommand)('instructions-loaded')));
488
- program.parseAsync(process.argv).catch(err => {
489
- const msg = err instanceof Error ? err.message : String(err);
490
- console.error(`Error: ${msg}`);
491
- process.exit(1);
492
- });
512
+ if (!isUpdateCheckWorker) {
513
+ program.parseAsync(process.argv).catch(err => {
514
+ const msg = err instanceof Error ? err.message : String(err);
515
+ console.error(`Error: ${msg}`);
516
+ process.exit(1);
517
+ });
518
+ }
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isNewer = isNewer;
37
+ exports.fetchLatestVersion = fetchLatestVersion;
38
+ exports.printUpdateNoticeIfAny = printUpdateNoticeIfAny;
39
+ exports.maybeRefreshInBackground = maybeRefreshInBackground;
40
+ exports.runBackgroundRefresh = runBackgroundRefresh;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ const https = __importStar(require("https"));
45
+ const child_process_1 = require("child_process");
46
+ const CACHE_FILE = path.join(process.env.LUMO_CONFIG_DIR || path.join(os.homedir(), '.lumo'), 'update-check.json');
47
+ const CHECK_INTERVAL_MS = 1000 * 60 * 60 * 24;
48
+ function readCache() {
49
+ try {
50
+ const raw = fs.readFileSync(CACHE_FILE, 'utf8');
51
+ const parsed = JSON.parse(raw);
52
+ if (typeof parsed.lastChecked !== 'number' ||
53
+ typeof parsed.latest !== 'string')
54
+ return null;
55
+ return parsed;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ function writeCache(cache) {
62
+ try {
63
+ fs.mkdirSync(path.dirname(CACHE_FILE), { recursive: true });
64
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(cache));
65
+ }
66
+ catch {
67
+ /* swallow — the update check is non-essential */
68
+ }
69
+ }
70
+ // Compare two dotted version strings. Returns true iff `b` is strictly newer.
71
+ // Handles the subset of semver we actually publish: MAJOR.MINOR.PATCH with
72
+ // no pre-release tags. Unknown segments are treated as 0.
73
+ function isNewer(current, candidate) {
74
+ const c = current.split('.').map(n => parseInt(n, 10) || 0);
75
+ const l = candidate.split('.').map(n => parseInt(n, 10) || 0);
76
+ for (let i = 0; i < 3; i++) {
77
+ const cv = c[i] ?? 0;
78
+ const lv = l[i] ?? 0;
79
+ if (lv > cv)
80
+ return true;
81
+ if (lv < cv)
82
+ return false;
83
+ }
84
+ return false;
85
+ }
86
+ function fetchLatestVersion(name, timeoutMs = 2000) {
87
+ return new Promise(resolve => {
88
+ const url = `https://registry.npmjs.org/${encodeURIComponent(name).replace('%40', '@')}/latest`;
89
+ const req = https.get(url, { headers: { Accept: 'application/json' } }, res => {
90
+ if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
91
+ res.resume();
92
+ return resolve(null);
93
+ }
94
+ let data = '';
95
+ res.setEncoding('utf8');
96
+ res.on('data', chunk => {
97
+ data += chunk;
98
+ });
99
+ res.on('end', () => {
100
+ try {
101
+ const json = JSON.parse(data);
102
+ resolve(typeof json.version === 'string' ? json.version : null);
103
+ }
104
+ catch {
105
+ resolve(null);
106
+ }
107
+ });
108
+ });
109
+ req.on('error', () => resolve(null));
110
+ req.setTimeout(timeoutMs, () => {
111
+ req.destroy();
112
+ resolve(null);
113
+ });
114
+ });
115
+ }
116
+ // Read cached latest version and emit a one-line notice to stderr when a
117
+ // newer version is available. Safe to call on every CLI invocation.
118
+ function printUpdateNoticeIfAny(name, current) {
119
+ const cache = readCache();
120
+ if (!cache)
121
+ return;
122
+ if (isNewer(current, cache.latest)) {
123
+ process.stderr.write(`\n ⬆ Update available: ${name} ${current} → ${cache.latest}\n Run: lumo update\n\n`);
124
+ }
125
+ }
126
+ // Spawn a detached child to refresh the cached latest version when the cache
127
+ // is older than CHECK_INTERVAL_MS. The child re-enters the CLI entrypoint via
128
+ // the `__update-check` sentinel arg and exits silently when done. Failures
129
+ // here must never affect the foreground command.
130
+ function maybeRefreshInBackground(name) {
131
+ const cache = readCache();
132
+ if (cache && Date.now() - cache.lastChecked < CHECK_INTERVAL_MS)
133
+ return;
134
+ try {
135
+ const child = (0, child_process_1.spawn)(process.execPath, [process.argv[1] || '', '__update-check', name], { detached: true, stdio: 'ignore' });
136
+ child.unref();
137
+ }
138
+ catch {
139
+ /* swallow */
140
+ }
141
+ }
142
+ // Entrypoint for the detached child. Called from index.ts when argv[2] is
143
+ // the sentinel; performs the fetch and writes the cache, then exits.
144
+ async function runBackgroundRefresh(name) {
145
+ const latest = await fetchLatestVersion(name, 5000);
146
+ if (latest)
147
+ writeCache({ lastChecked: Date.now(), latest });
148
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumoai/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Lumo CLI — manage tasks and sessions from the terminal",
5
5
  "license": "MIT",
6
6
  "author": "cli@uselumo.ai",
@@ -24,7 +24,7 @@
24
24
  "node": ">=18"
25
25
  },
26
26
  "bin": {
27
- "lumo": "./dist/cli/src/index.js"
27
+ "lumo": "dist/cli/src/index.js"
28
28
  },
29
29
  "files": [
30
30
  "dist"