peaks-cli 1.0.24 → 1.0.25
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/src/cli/commands/core-artifact-commands.js +43 -1
- package/dist/src/services/session/index.d.ts +1 -1
- package/dist/src/services/session/index.js +1 -1
- package/dist/src/services/session/session-manager.d.ts +29 -0
- package/dist/src/services/session/session-manager.js +90 -2
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-solo/SKILL.md +10 -0
|
@@ -8,6 +8,7 @@ import { runDoctor } from '../../services/doctor/doctor-service.js';
|
|
|
8
8
|
import { listSkills } from '../../services/skills/skill-registry.js';
|
|
9
9
|
import { inspectSkillRunbook } from '../../services/skills/skill-runbook-service.js';
|
|
10
10
|
import { setSkillPresence, clearSkillPresence, getSkillPresence, isSkillPresenceMode, touchSkillHeartbeat } from '../../services/skills/skill-presence-service.js';
|
|
11
|
+
import { ensureSession, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas } from '../../services/session/session-manager.js';
|
|
11
12
|
import { fail, ok } from '../../shared/result.js';
|
|
12
13
|
import { addJsonOption, failUnsupportedNonDryRun, getErrorMessage, isArtifactProvider, isArtifactSetupStep, printResult } from '../cli-helpers.js';
|
|
13
14
|
export function registerCoreAndArtifactCommands(program, io) {
|
|
@@ -71,13 +72,21 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
71
72
|
.command('presence:set <name>')
|
|
72
73
|
.description('Set the currently active Peaks skill for session-wide visibility')
|
|
73
74
|
.option('--mode <mode>', 'execution mode')
|
|
74
|
-
.option('--gate <gate>', 'current gate')).action((name, options) => {
|
|
75
|
+
.option('--gate <gate>', 'current gate')).action(async (name, options) => {
|
|
75
76
|
if (options.mode !== undefined && !isSkillPresenceMode(options.mode)) {
|
|
76
77
|
printResult(io, fail('skill.presence:set', 'INVALID_MODE', `Invalid mode: ${options.mode} (expected one of: full-auto, assisted, swarm, strict)`, { name, mode: options.mode }, ['Use a valid mode: full-auto, assisted, swarm, or strict']), options.json);
|
|
77
78
|
process.exitCode = 1;
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
81
|
const presence = setSkillPresence(name, options.mode, options.gate);
|
|
82
|
+
// Also update session metadata so session dirs self-document
|
|
83
|
+
const projectRoot = process.cwd();
|
|
84
|
+
const sessionId = await ensureSession(projectRoot);
|
|
85
|
+
setSessionMeta(projectRoot, sessionId, {
|
|
86
|
+
skill: name,
|
|
87
|
+
...(options.mode ? { mode: options.mode } : {}),
|
|
88
|
+
...(options.gate ? { gate: options.gate } : {})
|
|
89
|
+
});
|
|
81
90
|
printResult(io, ok('skill.presence:set', { active: true, ...presence }), options.json);
|
|
82
91
|
});
|
|
83
92
|
addJsonOption(skill
|
|
@@ -116,6 +125,39 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
116
125
|
lastHeartbeat: updated.lastHeartbeat
|
|
117
126
|
}), options.json);
|
|
118
127
|
});
|
|
128
|
+
const session = program.command('session').description('Manage Peaks session directories');
|
|
129
|
+
addJsonOption(session
|
|
130
|
+
.command('list')
|
|
131
|
+
.description('List all session directories with titles and metadata')).action((options) => {
|
|
132
|
+
const projectRoot = process.cwd();
|
|
133
|
+
const metas = listSessionMetas(projectRoot);
|
|
134
|
+
printResult(io, ok('session.list', { sessions: metas, total: metas.length }), options.json);
|
|
135
|
+
});
|
|
136
|
+
addJsonOption(session
|
|
137
|
+
.command('info <sessionId>')
|
|
138
|
+
.description('Show full metadata for a session directory')).action((sessionId, options) => {
|
|
139
|
+
const projectRoot = process.cwd();
|
|
140
|
+
const meta = getSessionMeta(projectRoot, sessionId);
|
|
141
|
+
if (meta === null) {
|
|
142
|
+
printResult(io, fail('session.info', 'SESSION_NOT_FOUND', `Session "${sessionId}" not found or has no metadata`, { sessionId }, ['Use `peaks session list` to see available sessions']), options.json);
|
|
143
|
+
process.exitCode = 1;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
printResult(io, ok('session.info', meta), options.json);
|
|
147
|
+
});
|
|
148
|
+
addJsonOption(session
|
|
149
|
+
.command('title <sessionId> <title>')
|
|
150
|
+
.description('Set a human-readable title for a session directory')).action((sessionId, title, options) => {
|
|
151
|
+
const projectRoot = process.cwd();
|
|
152
|
+
try {
|
|
153
|
+
const meta = setSessionTitle(projectRoot, sessionId, title);
|
|
154
|
+
printResult(io, ok('session.title', meta), options.json);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
printResult(io, fail('session.title', 'SESSION_TITLE_FAILED', getErrorMessage(error), { sessionId }, ['Verify the sessionId exists under .peaks/']), options.json);
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
119
161
|
const profile = program.command('profile').description('Manage runtime profiles');
|
|
120
162
|
addJsonOption(profile.command('list').description('List available profiles')).action((options) => {
|
|
121
163
|
printResult(io, ok('profile.list', { profiles: listProfiles() }), options.json);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ensureSession, getSessionId, getCurrentSessionDir, listSessions, getProjectScanPath, hasProjectScan, type SessionInfo } from './session-manager.js';
|
|
1
|
+
export { ensureSession, getSessionId, getCurrentSessionDir, listSessions, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas, getProjectScanPath, hasProjectScan, type SessionInfo, type SessionMeta } from './session-manager.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ensureSession, getSessionId, getCurrentSessionDir, listSessions, getProjectScanPath, hasProjectScan } from './session-manager.js';
|
|
1
|
+
export { ensureSession, getSessionId, getCurrentSessionDir, listSessions, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas, getProjectScanPath, hasProjectScan } from './session-manager.js';
|
|
@@ -10,6 +10,35 @@ export type SessionInfo = {
|
|
|
10
10
|
createdAt: string;
|
|
11
11
|
projectRoot: string;
|
|
12
12
|
};
|
|
13
|
+
export type SessionMeta = {
|
|
14
|
+
sessionId: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
skill?: string;
|
|
17
|
+
mode?: string;
|
|
18
|
+
gate?: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
lastActivity?: string;
|
|
21
|
+
projectRoot: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Read metadata for a specific session directory.
|
|
25
|
+
* Returns null if the session directory or its session.json does not exist.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getSessionMeta(projectRoot: string, sessionId: string): SessionMeta | null;
|
|
28
|
+
/**
|
|
29
|
+
* Write or update metadata for a session. Fields besides sessionId and createdAt
|
|
30
|
+
* are merged on top of the current meta (partial update).
|
|
31
|
+
*/
|
|
32
|
+
export declare function setSessionMeta(projectRoot: string, sessionId: string, partial: Partial<Omit<SessionMeta, 'sessionId' | 'createdAt' | 'projectRoot'>>): SessionMeta;
|
|
33
|
+
/**
|
|
34
|
+
* Set the display title for a session directory.
|
|
35
|
+
*/
|
|
36
|
+
export declare function setSessionTitle(projectRoot: string, sessionId: string, title: string): SessionMeta;
|
|
37
|
+
/**
|
|
38
|
+
* List all session directories under .peaks with their metadata.
|
|
39
|
+
* Returns sessions sorted by sessionId descending (most recent first).
|
|
40
|
+
*/
|
|
41
|
+
export declare function listSessionMetas(projectRoot: string): SessionMeta[];
|
|
13
42
|
/**
|
|
14
43
|
* Get or create the current session for a project.
|
|
15
44
|
* If a valid session already exists, returns it.
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* Sessions are automatically created when any skill is invoked.
|
|
6
6
|
* Each session gets a unique directory under .peaks/ with incrementing numbered files.
|
|
7
7
|
*/
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { randomBytes } from 'node:crypto';
|
|
11
11
|
import { initWorkspace } from '../workspace/workspace-service.js';
|
|
12
12
|
const SESSION_FILE = '.session.json';
|
|
13
|
+
const META_FILE = 'session.json';
|
|
13
14
|
/**
|
|
14
15
|
* Generate a new session ID.
|
|
15
16
|
* Format: YYYY-MM-DD-session-<6位hex>
|
|
@@ -60,6 +61,86 @@ function writeSessionFile(projectRoot, info) {
|
|
|
60
61
|
}
|
|
61
62
|
writeFileSync(sessionFile, JSON.stringify(info, null, 2), 'utf8');
|
|
62
63
|
}
|
|
64
|
+
function getMetaFilePath(projectRoot, sessionId) {
|
|
65
|
+
return join(projectRoot, '.peaks', sessionId, META_FILE);
|
|
66
|
+
}
|
|
67
|
+
function readSessionMeta(projectRoot, sessionId) {
|
|
68
|
+
const metaPath = getMetaFilePath(projectRoot, sessionId);
|
|
69
|
+
if (!existsSync(metaPath))
|
|
70
|
+
return null;
|
|
71
|
+
try {
|
|
72
|
+
const raw = readFileSync(metaPath, 'utf8');
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
if (typeof parsed?.sessionId !== 'string' || parsed.sessionId.length === 0) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function writeSessionMeta(projectRoot, sessionId, meta) {
|
|
84
|
+
const metaPath = getMetaFilePath(projectRoot, sessionId);
|
|
85
|
+
const metaDir = join(projectRoot, '.peaks', sessionId);
|
|
86
|
+
if (!existsSync(metaDir)) {
|
|
87
|
+
mkdirSync(metaDir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf8');
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Read metadata for a specific session directory.
|
|
93
|
+
* Returns null if the session directory or its session.json does not exist.
|
|
94
|
+
*/
|
|
95
|
+
export function getSessionMeta(projectRoot, sessionId) {
|
|
96
|
+
return readSessionMeta(projectRoot, sessionId);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Write or update metadata for a session. Fields besides sessionId and createdAt
|
|
100
|
+
* are merged on top of the current meta (partial update).
|
|
101
|
+
*/
|
|
102
|
+
export function setSessionMeta(projectRoot, sessionId, partial) {
|
|
103
|
+
const existing = readSessionMeta(projectRoot, sessionId);
|
|
104
|
+
const now = new Date().toISOString();
|
|
105
|
+
const meta = existing
|
|
106
|
+
? { ...existing, ...partial, lastActivity: now }
|
|
107
|
+
: {
|
|
108
|
+
sessionId,
|
|
109
|
+
projectRoot,
|
|
110
|
+
createdAt: now,
|
|
111
|
+
...partial,
|
|
112
|
+
lastActivity: now
|
|
113
|
+
};
|
|
114
|
+
writeSessionMeta(projectRoot, sessionId, meta);
|
|
115
|
+
return meta;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Set the display title for a session directory.
|
|
119
|
+
*/
|
|
120
|
+
export function setSessionTitle(projectRoot, sessionId, title) {
|
|
121
|
+
return setSessionMeta(projectRoot, sessionId, { title });
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* List all session directories under .peaks with their metadata.
|
|
125
|
+
* Returns sessions sorted by sessionId descending (most recent first).
|
|
126
|
+
*/
|
|
127
|
+
export function listSessionMetas(projectRoot) {
|
|
128
|
+
const peaksRoot = join(projectRoot, '.peaks');
|
|
129
|
+
if (!existsSync(peaksRoot))
|
|
130
|
+
return [];
|
|
131
|
+
const entries = readdirSync(peaksRoot, { withFileTypes: true });
|
|
132
|
+
return entries
|
|
133
|
+
.filter((entry) => entry.isDirectory() && /^\d{4}-\d{2}-\d{2}-session-[a-f0-9]+$/.test(entry.name))
|
|
134
|
+
.map((entry) => {
|
|
135
|
+
const meta = readSessionMeta(projectRoot, entry.name);
|
|
136
|
+
return meta ?? {
|
|
137
|
+
sessionId: entry.name,
|
|
138
|
+
projectRoot,
|
|
139
|
+
createdAt: ''
|
|
140
|
+
};
|
|
141
|
+
})
|
|
142
|
+
.sort((a, b) => b.sessionId.localeCompare(a.sessionId));
|
|
143
|
+
}
|
|
63
144
|
/**
|
|
64
145
|
* Get or create the current session for a project.
|
|
65
146
|
* If a valid session already exists, returns it.
|
|
@@ -74,13 +155,20 @@ export async function ensureSession(projectRoot) {
|
|
|
74
155
|
return existing.sessionId;
|
|
75
156
|
}
|
|
76
157
|
const sessionId = generateSessionId();
|
|
158
|
+
const now = new Date().toISOString();
|
|
77
159
|
const info = {
|
|
78
160
|
sessionId,
|
|
79
|
-
createdAt:
|
|
161
|
+
createdAt: now,
|
|
80
162
|
projectRoot
|
|
81
163
|
};
|
|
82
164
|
writeSessionFile(projectRoot, info);
|
|
83
165
|
await initWorkspace({ projectRoot, sessionId });
|
|
166
|
+
// Initialize session metadata inside the session directory
|
|
167
|
+
writeSessionMeta(projectRoot, sessionId, {
|
|
168
|
+
sessionId,
|
|
169
|
+
projectRoot,
|
|
170
|
+
createdAt: now
|
|
171
|
+
});
|
|
84
172
|
return sessionId;
|
|
85
173
|
}
|
|
86
174
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "1.0.
|
|
1
|
+
export declare const CLI_VERSION = "1.0.25";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.0.
|
|
1
|
+
export const CLI_VERSION = "1.0.25";
|
package/package.json
CHANGED
|
@@ -78,6 +78,16 @@ Then display the compact status header: `Peaks-Cli Skill: peaks-solo | Peaks-Cli
|
|
|
78
78
|
|
|
79
79
|
Update with `peaks skill presence:set peaks-solo --mode <mode> --gate <gate>` when gates change. The presence file persists across the full workflow lifecycle — do NOT clear it at workflow end.
|
|
80
80
|
|
|
81
|
+
### Peaks-Cli Step 2.5: Set session title
|
|
82
|
+
|
|
83
|
+
Extract a short (8-20 Chinese characters, or 4-10 English words) descriptive title from the user's first request. The title should capture the core task — e.g. "修复登录页OAuth回调异常", "添加暗色模式开关", "搭建项目基础架构". Then run:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
peaks session title $(cat .peaks/.session.json | python3 -c "import sys,json; print(json.load(sys.stdin)['sessionId'])") "<title>"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If the session directory already has a title (check via `peaks session list --json`), skip this step — the title is already set.
|
|
90
|
+
|
|
81
91
|
## Boundaries
|
|
82
92
|
|
|
83
93
|
Peaks-Cli Solo may:
|