claude-flow 3.56.2 → 3.56.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.
- package/.claude/helpers/auto-memory-hook.mjs +13 -8
- package/.claude/helpers/hook-handler.cjs +19 -14
- package/.claude/helpers/learning-service.mjs +5 -2
- package/.claude/helpers/session.cjs +56 -10
- package/.claude/helpers/statusline.cjs +1338 -1328
- package/.claude-plugin/scripts/ruflo-hook.cjs +14 -2
- package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts +1 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/resource-registry.js +10 -4
- package/node_modules/@claude-flow/mcp/dist/resource-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/sampling.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/sampling.js +13 -6
- package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.js +10 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts +1 -0
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +17 -11
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/index.d.ts +1 -1
- package/node_modules/@claude-flow/security/dist/index.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/index.js +1 -1
- package/node_modules/@claude-flow/security/dist/index.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts +9 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js +15 -2
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +4 -4
- package/v3/@claude-flow/cli/README.md +62 -158
- package/v3/@claude-flow/cli/catalog-manifest.json +5 -5
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +7 -13
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +3 -6
- package/v3/@claude-flow/cli/dist/src/commands/init.js +3 -10
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +9 -14
- package/v3/@claude-flow/cli/dist/src/commands/plugins.js +81 -8
- package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.d.ts +40 -0
- package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.js +192 -0
- package/v3/@claude-flow/cli/dist/src/commands/security.js +8 -11
- package/v3/@claude-flow/cli/dist/src/commands/status.js +8 -10
- package/v3/@claude-flow/cli/dist/src/init/executor.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +25 -16
- package/v3/@claude-flow/cli/dist/src/init/helper-commands.d.ts +18 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-commands.js +142 -0
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +128 -29
- package/v3/@claude-flow/cli/dist/src/init/settings-generator.js +36 -103
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +6 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +7 -10
- package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +24 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/testgen-tools.js +8 -12
- package/v3/@claude-flow/cli/dist/src/mcp-tools/transfer-tools.js +9 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +10 -4
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +10 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +2 -19
- package/v3/@claude-flow/cli/dist/src/memory/shared-sqlite.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/memory/shared-sqlite.js +35 -7
- package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +3 -1
- package/v3/@claude-flow/cli/dist/src/plugins/manager.js +31 -10
- package/v3/@claude-flow/cli/dist/src/plugins/own-install.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/plugins/own-install.js +51 -0
- package/v3/@claude-flow/cli/dist/src/plugins/store/discovery.d.ts +26 -2
- package/v3/@claude-flow/cli/dist/src/plugins/store/discovery.js +108 -81
- package/v3/@claude-flow/cli/dist/src/plugins/store/index.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/plugins/store/index.js +1 -1
- package/v3/@claude-flow/cli/dist/src/plugins/store/types.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +29 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +18 -6
- package/v3/@claude-flow/cli/dist/src/transfer/ipfs/client.js +3 -1
- package/v3/@claude-flow/cli/package.json +6 -6
|
@@ -17,11 +17,16 @@ import { fileURLToPath } from 'url';
|
|
|
17
17
|
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = dirname(__filename);
|
|
20
|
+
// Executable modules resolve only from the helper's own install root. A
|
|
21
|
+
// home-level helper can serve an untrusted project, so CLAUDE_PROJECT_DIR must
|
|
22
|
+
// never decide which JavaScript gets imported.
|
|
23
|
+
const MODULE_ROOT = join(__dirname, '../..');
|
|
20
24
|
// Home-level helpers can serve a different project. Keep the memory writer on
|
|
21
25
|
// the same project root as intelligence.cjs, which reads CLAUDE_PROJECT_DIR.
|
|
26
|
+
// PROJECT_ROOT is for data only.
|
|
22
27
|
const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
|
|
23
28
|
? resolve(process.env.CLAUDE_PROJECT_DIR)
|
|
24
|
-
:
|
|
29
|
+
: MODULE_ROOT;
|
|
25
30
|
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
|
|
26
31
|
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
|
|
27
32
|
|
|
@@ -41,7 +46,7 @@ const dim = (msg) => console.log(` ${DIM}${msg}${RESET}`);
|
|
|
41
46
|
// told exactly how to fix it (on both stdout, so it shows in the Claude Code hook
|
|
42
47
|
// transcript, and stderr, per the issue's requested channel).
|
|
43
48
|
function warnMemoryUnavailable() {
|
|
44
|
-
const line1 = `[AutoMemory] @claude-flow/memory not resolvable from ${
|
|
49
|
+
const line1 = `[AutoMemory] @claude-flow/memory not resolvable from ${MODULE_ROOT} — self-learning imports are DISABLED.`;
|
|
45
50
|
const line2 = ' Fix: npm i -D @claude-flow/memory (or re-run: npx ruflo@latest init, then npx ruflo@latest doctor --fix)';
|
|
46
51
|
console.log(`${YELLOW}${line1}${RESET}`);
|
|
47
52
|
console.log(`${YELLOW}${line2}${RESET}`);
|
|
@@ -175,7 +180,7 @@ async function loadMemoryPackage() {
|
|
|
175
180
|
// project — so init resolves it from the CLI's own context and records the
|
|
176
181
|
// absolute path here. This is the only strategy that works on that install.
|
|
177
182
|
try {
|
|
178
|
-
const sidecar = join(
|
|
183
|
+
const sidecar = join(MODULE_ROOT, '.claude-flow', 'memory-package.json');
|
|
179
184
|
if (existsSync(sidecar)) {
|
|
180
185
|
const rec = JSON.parse(readFileSync(sidecar, 'utf-8'));
|
|
181
186
|
if (rec?.distPath && existsSync(rec.distPath)) {
|
|
@@ -185,7 +190,7 @@ async function loadMemoryPackage() {
|
|
|
185
190
|
} catch { /* fall through */ }
|
|
186
191
|
|
|
187
192
|
// Strategy 1: Local dev (built dist)
|
|
188
|
-
const localDist = join(
|
|
193
|
+
const localDist = join(MODULE_ROOT, 'v3/@claude-flow/memory/dist/index.js');
|
|
189
194
|
if (existsSync(localDist)) {
|
|
190
195
|
try {
|
|
191
196
|
return await import(`file://${localDist}`);
|
|
@@ -196,7 +201,7 @@ async function loadMemoryPackage() {
|
|
|
196
201
|
// when installed as a transitive dependency via npx ruflo / npx claude-flow)
|
|
197
202
|
try {
|
|
198
203
|
const { createRequire } = await import('module');
|
|
199
|
-
const require = createRequire(join(
|
|
204
|
+
const require = createRequire(join(MODULE_ROOT, 'package.json'));
|
|
200
205
|
return require('@claude-flow/memory');
|
|
201
206
|
} catch { /* fall through */ }
|
|
202
207
|
|
|
@@ -205,8 +210,8 @@ async function loadMemoryPackage() {
|
|
|
205
210
|
return await import('@claude-flow/memory');
|
|
206
211
|
} catch { /* fall through */ }
|
|
207
212
|
|
|
208
|
-
// Strategy 4: Walk up from
|
|
209
|
-
let searchDir =
|
|
213
|
+
// Strategy 4: Walk up from MODULE_ROOT looking for @claude-flow/memory in any node_modules
|
|
214
|
+
let searchDir = MODULE_ROOT;
|
|
210
215
|
const { parse } = await import('path');
|
|
211
216
|
while (searchDir !== parse(searchDir).root) {
|
|
212
217
|
const candidate = join(searchDir, 'node_modules', '@claude-flow', 'memory', 'dist', 'index.js');
|
|
@@ -378,7 +383,7 @@ async function doStatus() {
|
|
|
378
383
|
const memPkg = await loadMemoryPackage();
|
|
379
384
|
const config = readConfig();
|
|
380
385
|
|
|
381
|
-
const sidecar = join(
|
|
386
|
+
const sidecar = join(MODULE_ROOT, '.claude-flow', 'memory-package.json');
|
|
382
387
|
const hasSidecar = existsSync(sidecar);
|
|
383
388
|
|
|
384
389
|
console.log('\n=== Auto Memory Bridge Status ===\n');
|
|
@@ -18,6 +18,10 @@ const fs = require('fs');
|
|
|
18
18
|
const os = require('os');
|
|
19
19
|
|
|
20
20
|
const helpersDir = __dirname;
|
|
21
|
+
// The helper's own install root: the project for a project-local helper,
|
|
22
|
+
// $HOME for a home-level one. CLI resolution starts here, never at the opened
|
|
23
|
+
// project, so a home-level helper does not run code the project supplies.
|
|
24
|
+
const helperRoot = path.resolve(helpersDir, '..', '..');
|
|
21
25
|
|
|
22
26
|
// Resolve an installed @claude-flow/cli (or ruflo) bin — mirrors
|
|
23
27
|
// statusline-generator.ts's resolveCliBin() candidate list. Used only to
|
|
@@ -38,12 +42,11 @@ const helpersDir = __dirname;
|
|
|
38
42
|
function resolveCliBinForHook() {
|
|
39
43
|
try {
|
|
40
44
|
const home = os.homedir();
|
|
41
|
-
const cwd = process.cwd();
|
|
42
45
|
const candidates = [
|
|
43
46
|
path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'bin', 'cli.js'),
|
|
44
|
-
path.join(
|
|
45
|
-
path.join(
|
|
46
|
-
path.join(
|
|
47
|
+
path.join(helperRoot, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
48
|
+
path.join(helperRoot, 'node_modules', 'ruflo', 'bin', 'cli.js'),
|
|
49
|
+
path.join(helperRoot, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
47
50
|
// helpersDir is .claude/helpers/ inside the package itself when this
|
|
48
51
|
// file is running from a real @claude-flow/cli install (not a project
|
|
49
52
|
// that merely copied the helper) — its bin/ is two levels up.
|
|
@@ -77,14 +80,19 @@ function resolveCliBinForHook() {
|
|
|
77
80
|
// render path, where local-first exists purely for per-render latency.
|
|
78
81
|
// `--prefer-offline` avoids a registry round trip for the tarball when
|
|
79
82
|
// already cached while still resolving the current `@latest` version.
|
|
83
|
+
//
|
|
84
|
+
// `--prefix helperRoot` makes npx resolve @claude-flow/cli from the helper's
|
|
85
|
+
// install root instead of the cwd (the opened project); the spawned CLI still
|
|
86
|
+
// runs with the project as its cwd.
|
|
87
|
+
function cliSpawnArgs(cliBin, args, platform = process.platform) {
|
|
88
|
+
if (cliBin) return [process.execPath, [cliBin, ...args]];
|
|
89
|
+
return [platform === 'win32' ? 'npx.cmd' : 'npx', ['--prefer-offline', '--prefix', helperRoot, '@claude-flow/cli', ...args]];
|
|
90
|
+
}
|
|
91
|
+
|
|
80
92
|
function spawnDetachedHookRefresh(subcommand) {
|
|
81
93
|
try {
|
|
82
94
|
const { spawn } = require('child_process');
|
|
83
|
-
const
|
|
84
|
-
const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
85
|
-
const spawnArgs = cliBin
|
|
86
|
-
? [process.execPath, [cliBin, 'hooks', subcommand, '--quiet']]
|
|
87
|
-
: [cmd, ['--prefer-offline', '@claude-flow/cli', 'hooks', subcommand, '--quiet']];
|
|
95
|
+
const spawnArgs = cliSpawnArgs(resolveCliBinForHook(), ['hooks', subcommand, '--quiet']);
|
|
88
96
|
const child = spawn(spawnArgs[0], spawnArgs[1], {
|
|
89
97
|
detached: true,
|
|
90
98
|
stdio: 'ignore',
|
|
@@ -151,10 +159,7 @@ function firstRunAutoEnableIfEligible() {
|
|
|
151
159
|
const cliBin = resolveCliBinForHook();
|
|
152
160
|
const runDetached = (args) => {
|
|
153
161
|
try {
|
|
154
|
-
const spawnArgs = cliBin
|
|
155
|
-
? [process.execPath, [cliBin, ...args]]
|
|
156
|
-
: [process.platform === 'win32' ? 'npx.cmd' : 'npx',
|
|
157
|
-
['--prefer-offline', '@claude-flow/cli', ...args]];
|
|
162
|
+
const spawnArgs = cliSpawnArgs(cliBin, args);
|
|
158
163
|
const child = spawn(spawnArgs[0], spawnArgs[1], {
|
|
159
164
|
detached: true, stdio: 'ignore', env: process.env, windowsHide: true,
|
|
160
165
|
});
|
|
@@ -772,4 +777,4 @@ if (require.main === module) {
|
|
|
772
777
|
// Which sibling helpers loaded (all CommonJS, shipped as .cjs — #3555).
|
|
773
778
|
const loadedHelpers = { router: !!router, session: !!session, memory: !!memory, intelligence: !!intelligence };
|
|
774
779
|
|
|
775
|
-
module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS };
|
|
780
|
+
module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS, resolveCliBinForHook, cliSpawnArgs, helperRoot };
|
|
@@ -26,9 +26,12 @@ import Database from 'better-sqlite3';
|
|
|
26
26
|
|
|
27
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
28
28
|
const __dirname = dirname(__filename);
|
|
29
|
+
// Executable modules resolve only from the helper's own install root; a
|
|
30
|
+
// home-level helper can serve an untrusted project. PROJECT_ROOT is data only.
|
|
31
|
+
const MODULE_ROOT = join(__dirname, '../..');
|
|
29
32
|
const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
|
|
30
33
|
? resolve(process.env.CLAUDE_PROJECT_DIR)
|
|
31
|
-
:
|
|
34
|
+
: MODULE_ROOT;
|
|
32
35
|
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow/learning');
|
|
33
36
|
const DB_PATH = join(DATA_DIR, 'patterns.db');
|
|
34
37
|
const METRICS_PATH = join(DATA_DIR, 'learning-metrics.json');
|
|
@@ -463,7 +466,7 @@ class EmbeddingService {
|
|
|
463
466
|
|
|
464
467
|
try {
|
|
465
468
|
// Dynamically import agentic-flow OptimizedEmbedder
|
|
466
|
-
const agenticFlowPath = join(
|
|
469
|
+
const agenticFlowPath = join(MODULE_ROOT, 'node_modules/agentic-flow/dist/embeddings/optimized-embedder.js');
|
|
467
470
|
|
|
468
471
|
if (existsSync(agenticFlowPath)) {
|
|
469
472
|
const { getOptimizedEmbedder } = await import(agenticFlowPath);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const crypto = require('crypto');
|
|
9
10
|
const os = require('os');
|
|
10
11
|
|
|
11
12
|
const platform = os.platform();
|
|
@@ -30,6 +31,49 @@ function getDataDir() {
|
|
|
30
31
|
const SESSION_DIR = getDataDir();
|
|
31
32
|
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
|
|
32
33
|
|
|
34
|
+
// Archive names are generated by start(); anything else in current.json is
|
|
35
|
+
// untrusted project content and must never pick a path.
|
|
36
|
+
const SESSION_ID = /^session-\d+$/;
|
|
37
|
+
|
|
38
|
+
// current.json lives in the opened project. A symlink there must never be read
|
|
39
|
+
// or written through, so only a regular file counts as an active session.
|
|
40
|
+
function hasSessionFile() {
|
|
41
|
+
try { return fs.lstatSync(SESSION_FILE).isFile(); } catch { return false; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function archiveId(id) {
|
|
45
|
+
return SESSION_ID.test(String(id)) ? String(id) : `session-${Date.now()}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The project session directory must resolve inside the project (no
|
|
49
|
+
// symlinked escape). The per-user fallback directory is not project-owned.
|
|
50
|
+
function assertSessionDirContained() {
|
|
51
|
+
if (SESSION_DIR !== path.join(process.cwd(), '.claude-flow', 'sessions')) return;
|
|
52
|
+
const root = fs.realpathSync(process.cwd());
|
|
53
|
+
// Resolve the deepest part of the path that exists (lstat: a symlink counts),
|
|
54
|
+
// so a symlinked .claude-flow is refused before anything is created under it.
|
|
55
|
+
let dir = SESSION_DIR;
|
|
56
|
+
for (;;) {
|
|
57
|
+
try { fs.lstatSync(dir); break; } catch { /* not created yet */ }
|
|
58
|
+
const parent = path.dirname(dir);
|
|
59
|
+
if (parent === dir) break;
|
|
60
|
+
dir = parent;
|
|
61
|
+
}
|
|
62
|
+
const rel = path.relative(root, fs.realpathSync(dir));
|
|
63
|
+
if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
64
|
+
throw new Error('Session directory resolves outside the project');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Exclusive temp file + rename: never writes through a planted symlink, and
|
|
69
|
+
// rename() replaces a symlinked target instead of following it.
|
|
70
|
+
function atomicWrite(file, data) {
|
|
71
|
+
assertSessionDirContained();
|
|
72
|
+
const tmp = `${file}.tmp.${process.pid}.${crypto.randomBytes(6).toString('hex')}`;
|
|
73
|
+
fs.writeFileSync(tmp, data, { flag: 'wx' });
|
|
74
|
+
fs.renameSync(tmp, file);
|
|
75
|
+
}
|
|
76
|
+
|
|
33
77
|
function ensureDir(dir) {
|
|
34
78
|
if (!fs.existsSync(dir)) {
|
|
35
79
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -38,6 +82,7 @@ function ensureDir(dir) {
|
|
|
38
82
|
|
|
39
83
|
const commands = {
|
|
40
84
|
start: () => {
|
|
85
|
+
assertSessionDirContained();
|
|
41
86
|
ensureDir(SESSION_DIR);
|
|
42
87
|
const sessionId = `session-${Date.now()}`;
|
|
43
88
|
const session = {
|
|
@@ -48,25 +93,25 @@ const commands = {
|
|
|
48
93
|
context: {},
|
|
49
94
|
metrics: { edits: 0, commands: 0, tasks: 0, errors: 0 }
|
|
50
95
|
};
|
|
51
|
-
|
|
96
|
+
atomicWrite(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
52
97
|
console.log(`Session started: ${sessionId}`);
|
|
53
98
|
return session;
|
|
54
99
|
},
|
|
55
100
|
|
|
56
101
|
restore: () => {
|
|
57
|
-
if (!
|
|
102
|
+
if (!hasSessionFile()) {
|
|
58
103
|
console.log('No session to restore');
|
|
59
104
|
return null;
|
|
60
105
|
}
|
|
61
106
|
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
|
62
107
|
session.restoredAt = new Date().toISOString();
|
|
63
|
-
|
|
108
|
+
atomicWrite(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
64
109
|
console.log(`Session restored: ${session.id}`);
|
|
65
110
|
return session;
|
|
66
111
|
},
|
|
67
112
|
|
|
68
113
|
end: () => {
|
|
69
|
-
if (!
|
|
114
|
+
if (!hasSessionFile()) {
|
|
70
115
|
console.log('No active session');
|
|
71
116
|
return null;
|
|
72
117
|
}
|
|
@@ -74,17 +119,18 @@ const commands = {
|
|
|
74
119
|
session.endedAt = new Date().toISOString();
|
|
75
120
|
session.duration = Date.now() - new Date(session.startedAt).getTime();
|
|
76
121
|
|
|
77
|
-
|
|
78
|
-
|
|
122
|
+
// Archive under a validated name; a crafted id cannot pick the path.
|
|
123
|
+
const id = archiveId(session.id);
|
|
124
|
+
atomicWrite(path.join(SESSION_DIR, `${id}.json`), JSON.stringify(session, null, 2));
|
|
79
125
|
fs.unlinkSync(SESSION_FILE);
|
|
80
126
|
|
|
81
|
-
console.log(`Session ended: ${
|
|
127
|
+
console.log(`Session ended: ${id}`);
|
|
82
128
|
console.log(`Duration: ${Math.round(session.duration / 1000 / 60)} minutes`);
|
|
83
129
|
return session;
|
|
84
130
|
},
|
|
85
131
|
|
|
86
132
|
status: () => {
|
|
87
|
-
if (!
|
|
133
|
+
if (!hasSessionFile()) {
|
|
88
134
|
console.log('No active session');
|
|
89
135
|
return null;
|
|
90
136
|
}
|
|
@@ -98,13 +144,13 @@ const commands = {
|
|
|
98
144
|
},
|
|
99
145
|
|
|
100
146
|
metric: (name) => {
|
|
101
|
-
if (!
|
|
147
|
+
if (!hasSessionFile()) {
|
|
102
148
|
return null;
|
|
103
149
|
}
|
|
104
150
|
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
|
105
151
|
if (session.metrics[name] !== undefined) {
|
|
106
152
|
session.metrics[name]++;
|
|
107
|
-
|
|
153
|
+
atomicWrite(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
108
154
|
}
|
|
109
155
|
return session;
|
|
110
156
|
}
|