@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 +9 -7
- package/dist/cli/src/commands/update.js +71 -0
- package/dist/cli/src/index.js +31 -5
- package/dist/cli/src/lib/update-check.js +148 -0
- package/package.json +2 -2
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
|
+
}
|
package/dist/cli/src/index.js
CHANGED
|
@@ -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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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.
|
|
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": "
|
|
27
|
+
"lumo": "dist/cli/src/index.js"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist"
|