cc4pm 1.8.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/.claude-plugin/README.md +17 -0
- package/.claude-plugin/plugin.json +25 -0
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/README.zh-CN.md +134 -0
- package/contexts/dev.md +20 -0
- package/contexts/research.md +26 -0
- package/contexts/review.md +22 -0
- package/examples/CLAUDE.md +100 -0
- package/examples/statusline.json +19 -0
- package/examples/user-CLAUDE.md +109 -0
- package/install.sh +17 -0
- package/manifests/install-components.json +173 -0
- package/manifests/install-modules.json +335 -0
- package/manifests/install-profiles.json +75 -0
- package/package.json +117 -0
- package/schemas/ecc-install-config.schema.json +58 -0
- package/schemas/hooks.schema.json +197 -0
- package/schemas/install-components.schema.json +56 -0
- package/schemas/install-modules.schema.json +105 -0
- package/schemas/install-profiles.schema.json +45 -0
- package/schemas/install-state.schema.json +210 -0
- package/schemas/package-manager.schema.json +23 -0
- package/schemas/plugin.schema.json +58 -0
- package/scripts/ci/catalog.js +83 -0
- package/scripts/ci/validate-agents.js +81 -0
- package/scripts/ci/validate-commands.js +135 -0
- package/scripts/ci/validate-hooks.js +239 -0
- package/scripts/ci/validate-install-manifests.js +211 -0
- package/scripts/ci/validate-no-personal-paths.js +63 -0
- package/scripts/ci/validate-rules.js +81 -0
- package/scripts/ci/validate-skills.js +54 -0
- package/scripts/claw.js +468 -0
- package/scripts/doctor.js +110 -0
- package/scripts/ecc.js +194 -0
- package/scripts/hooks/auto-tmux-dev.js +88 -0
- package/scripts/hooks/check-console-log.js +71 -0
- package/scripts/hooks/check-hook-enabled.js +12 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +100 -0
- package/scripts/hooks/insaits-security-monitor.py +269 -0
- package/scripts/hooks/insaits-security-wrapper.js +88 -0
- package/scripts/hooks/post-bash-build-complete.js +27 -0
- package/scripts/hooks/post-bash-pr-created.js +36 -0
- package/scripts/hooks/post-edit-console-warn.js +54 -0
- package/scripts/hooks/post-edit-format.js +109 -0
- package/scripts/hooks/post-edit-typecheck.js +96 -0
- package/scripts/hooks/pre-bash-dev-server-block.js +187 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +28 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +33 -0
- package/scripts/hooks/pre-compact.js +48 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +168 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +120 -0
- package/scripts/hooks/session-end-marker.js +15 -0
- package/scripts/hooks/session-end.js +299 -0
- package/scripts/hooks/session-start.js +97 -0
- package/scripts/hooks/suggest-compact.js +80 -0
- package/scripts/install-apply.js +137 -0
- package/scripts/install-plan.js +254 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install/apply.js +23 -0
- package/scripts/lib/install/config.js +82 -0
- package/scripts/lib/install/request.js +113 -0
- package/scripts/lib/install/runtime.js +42 -0
- package/scripts/lib/install-executor.js +605 -0
- package/scripts/lib/install-lifecycle.js +763 -0
- package/scripts/lib/install-manifests.js +305 -0
- package/scripts/lib/install-state.js +120 -0
- package/scripts/lib/install-targets/antigravity-project.js +9 -0
- package/scripts/lib/install-targets/claude-home.js +10 -0
- package/scripts/lib/install-targets/codex-home.js +10 -0
- package/scripts/lib/install-targets/cursor-project.js +10 -0
- package/scripts/lib/install-targets/helpers.js +89 -0
- package/scripts/lib/install-targets/opencode-home.js +10 -0
- package/scripts/lib/install-targets/registry.js +64 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.d.ts +119 -0
- package/scripts/lib/package-manager.js +431 -0
- package/scripts/lib/project-detect.js +428 -0
- package/scripts/lib/resolve-formatter.js +185 -0
- package/scripts/lib/session-adapters/canonical-session.js +138 -0
- package/scripts/lib/session-adapters/claude-history.js +149 -0
- package/scripts/lib/session-adapters/dmux-tmux.js +80 -0
- package/scripts/lib/session-adapters/registry.js +111 -0
- package/scripts/lib/session-aliases.d.ts +136 -0
- package/scripts/lib/session-aliases.js +481 -0
- package/scripts/lib/session-manager.d.ts +131 -0
- package/scripts/lib/session-manager.js +464 -0
- package/scripts/lib/shell-split.js +86 -0
- package/scripts/lib/skill-improvement/amendify.js +89 -0
- package/scripts/lib/skill-improvement/evaluate.js +59 -0
- package/scripts/lib/skill-improvement/health.js +118 -0
- package/scripts/lib/skill-improvement/observations.js +108 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +491 -0
- package/scripts/lib/utils.d.ts +183 -0
- package/scripts/lib/utils.js +543 -0
- package/scripts/list-installed.js +90 -0
- package/scripts/orchestrate-codex-worker.sh +92 -0
- package/scripts/orchestrate-worktrees.js +108 -0
- package/scripts/orchestration-status.js +62 -0
- package/scripts/repair.js +97 -0
- package/scripts/session-inspect.js +150 -0
- package/scripts/setup-package-manager.js +204 -0
- package/scripts/skill-create-output.js +244 -0
- package/scripts/uninstall.js +96 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const SESSION_SCHEMA_VERSION = 'ecc.session.v1';
|
|
6
|
+
|
|
7
|
+
function buildAggregates(workers) {
|
|
8
|
+
const states = workers.reduce((accumulator, worker) => {
|
|
9
|
+
const state = worker.state || 'unknown';
|
|
10
|
+
accumulator[state] = (accumulator[state] || 0) + 1;
|
|
11
|
+
return accumulator;
|
|
12
|
+
}, {});
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
workerCount: workers.length,
|
|
16
|
+
states
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function deriveDmuxSessionState(snapshot) {
|
|
21
|
+
if (snapshot.sessionActive) {
|
|
22
|
+
return 'active';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (snapshot.workerCount > 0) {
|
|
26
|
+
return 'idle';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return 'missing';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeDmuxSnapshot(snapshot, sourceTarget) {
|
|
33
|
+
const workers = (snapshot.workers || []).map(worker => ({
|
|
34
|
+
id: worker.workerSlug,
|
|
35
|
+
label: worker.workerSlug,
|
|
36
|
+
state: worker.status.state || 'unknown',
|
|
37
|
+
branch: worker.status.branch || null,
|
|
38
|
+
worktree: worker.status.worktree || null,
|
|
39
|
+
runtime: {
|
|
40
|
+
kind: 'tmux-pane',
|
|
41
|
+
command: worker.pane ? worker.pane.currentCommand || null : null,
|
|
42
|
+
pid: worker.pane ? worker.pane.pid || null : null,
|
|
43
|
+
active: worker.pane ? Boolean(worker.pane.active) : false,
|
|
44
|
+
dead: worker.pane ? Boolean(worker.pane.dead) : false,
|
|
45
|
+
},
|
|
46
|
+
intent: {
|
|
47
|
+
objective: worker.task.objective || '',
|
|
48
|
+
seedPaths: Array.isArray(worker.task.seedPaths) ? worker.task.seedPaths : []
|
|
49
|
+
},
|
|
50
|
+
outputs: {
|
|
51
|
+
summary: Array.isArray(worker.handoff.summary) ? worker.handoff.summary : [],
|
|
52
|
+
validation: Array.isArray(worker.handoff.validation) ? worker.handoff.validation : [],
|
|
53
|
+
remainingRisks: Array.isArray(worker.handoff.remainingRisks) ? worker.handoff.remainingRisks : []
|
|
54
|
+
},
|
|
55
|
+
artifacts: {
|
|
56
|
+
statusFile: worker.files.status,
|
|
57
|
+
taskFile: worker.files.task,
|
|
58
|
+
handoffFile: worker.files.handoff
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
schemaVersion: SESSION_SCHEMA_VERSION,
|
|
64
|
+
adapterId: 'dmux-tmux',
|
|
65
|
+
session: {
|
|
66
|
+
id: snapshot.sessionName,
|
|
67
|
+
kind: 'orchestrated',
|
|
68
|
+
state: deriveDmuxSessionState(snapshot),
|
|
69
|
+
repoRoot: snapshot.repoRoot || null,
|
|
70
|
+
sourceTarget
|
|
71
|
+
},
|
|
72
|
+
workers,
|
|
73
|
+
aggregates: buildAggregates(workers)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function deriveClaudeWorkerId(session) {
|
|
78
|
+
if (session.shortId && session.shortId !== 'no-id') {
|
|
79
|
+
return session.shortId;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return path.basename(session.filename || session.sessionPath || 'session', '.tmp');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeClaudeHistorySession(session, sourceTarget) {
|
|
86
|
+
const metadata = session.metadata || {};
|
|
87
|
+
const workerId = deriveClaudeWorkerId(session);
|
|
88
|
+
const worker = {
|
|
89
|
+
id: workerId,
|
|
90
|
+
label: metadata.title || session.filename || workerId,
|
|
91
|
+
state: 'recorded',
|
|
92
|
+
branch: metadata.branch || null,
|
|
93
|
+
worktree: metadata.worktree || null,
|
|
94
|
+
runtime: {
|
|
95
|
+
kind: 'claude-session',
|
|
96
|
+
command: 'claude',
|
|
97
|
+
pid: null,
|
|
98
|
+
active: false,
|
|
99
|
+
dead: true,
|
|
100
|
+
},
|
|
101
|
+
intent: {
|
|
102
|
+
objective: metadata.inProgress && metadata.inProgress.length > 0
|
|
103
|
+
? metadata.inProgress[0]
|
|
104
|
+
: (metadata.title || ''),
|
|
105
|
+
seedPaths: []
|
|
106
|
+
},
|
|
107
|
+
outputs: {
|
|
108
|
+
summary: Array.isArray(metadata.completed) ? metadata.completed : [],
|
|
109
|
+
validation: [],
|
|
110
|
+
remainingRisks: metadata.notes ? [metadata.notes] : []
|
|
111
|
+
},
|
|
112
|
+
artifacts: {
|
|
113
|
+
sessionFile: session.sessionPath,
|
|
114
|
+
context: metadata.context || null
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
schemaVersion: SESSION_SCHEMA_VERSION,
|
|
120
|
+
adapterId: 'claude-history',
|
|
121
|
+
session: {
|
|
122
|
+
id: workerId,
|
|
123
|
+
kind: 'history',
|
|
124
|
+
state: 'recorded',
|
|
125
|
+
repoRoot: metadata.worktree || null,
|
|
126
|
+
sourceTarget
|
|
127
|
+
},
|
|
128
|
+
workers: [worker],
|
|
129
|
+
aggregates: buildAggregates([worker])
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
SESSION_SCHEMA_VERSION,
|
|
135
|
+
buildAggregates,
|
|
136
|
+
normalizeClaudeHistorySession,
|
|
137
|
+
normalizeDmuxSnapshot
|
|
138
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const sessionManager = require('../session-manager');
|
|
7
|
+
const sessionAliases = require('../session-aliases');
|
|
8
|
+
const { normalizeClaudeHistorySession } = require('./canonical-session');
|
|
9
|
+
|
|
10
|
+
function parseClaudeTarget(target) {
|
|
11
|
+
if (typeof target !== 'string') {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const prefix of ['claude-history:', 'claude:', 'history:']) {
|
|
16
|
+
if (target.startsWith(prefix)) {
|
|
17
|
+
return target.slice(prefix.length).trim();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isSessionFileTarget(target, cwd) {
|
|
25
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const absoluteTarget = path.resolve(cwd, target);
|
|
30
|
+
return fs.existsSync(absoluteTarget)
|
|
31
|
+
&& fs.statSync(absoluteTarget).isFile()
|
|
32
|
+
&& absoluteTarget.endsWith('.tmp');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hydrateSessionFromPath(sessionPath) {
|
|
36
|
+
const filename = path.basename(sessionPath);
|
|
37
|
+
const parsed = sessionManager.parseSessionFilename(filename);
|
|
38
|
+
if (!parsed) {
|
|
39
|
+
throw new Error(`Unsupported session file: ${sessionPath}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const content = sessionManager.getSessionContent(sessionPath);
|
|
43
|
+
const stats = fs.statSync(sessionPath);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...parsed,
|
|
47
|
+
sessionPath,
|
|
48
|
+
content,
|
|
49
|
+
metadata: sessionManager.parseSessionMetadata(content),
|
|
50
|
+
stats: sessionManager.getSessionStats(content || ''),
|
|
51
|
+
size: stats.size,
|
|
52
|
+
modifiedTime: stats.mtime,
|
|
53
|
+
createdTime: stats.birthtime || stats.ctime
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveSessionRecord(target, cwd) {
|
|
58
|
+
const explicitTarget = parseClaudeTarget(target);
|
|
59
|
+
|
|
60
|
+
if (explicitTarget) {
|
|
61
|
+
if (explicitTarget === 'latest') {
|
|
62
|
+
const [latest] = sessionManager.getAllSessions({ limit: 1 }).sessions;
|
|
63
|
+
if (!latest) {
|
|
64
|
+
throw new Error('No Claude session history found');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
session: sessionManager.getSessionById(latest.filename, true),
|
|
69
|
+
sourceTarget: {
|
|
70
|
+
type: 'claude-history',
|
|
71
|
+
value: 'latest'
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const alias = sessionAliases.resolveAlias(explicitTarget);
|
|
77
|
+
if (alias) {
|
|
78
|
+
return {
|
|
79
|
+
session: hydrateSessionFromPath(alias.sessionPath),
|
|
80
|
+
sourceTarget: {
|
|
81
|
+
type: 'claude-alias',
|
|
82
|
+
value: explicitTarget
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const session = sessionManager.getSessionById(explicitTarget, true);
|
|
88
|
+
if (!session) {
|
|
89
|
+
throw new Error(`Claude session not found: ${explicitTarget}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
session,
|
|
94
|
+
sourceTarget: {
|
|
95
|
+
type: 'claude-history',
|
|
96
|
+
value: explicitTarget
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isSessionFileTarget(target, cwd)) {
|
|
102
|
+
return {
|
|
103
|
+
session: hydrateSessionFromPath(path.resolve(cwd, target)),
|
|
104
|
+
sourceTarget: {
|
|
105
|
+
type: 'session-file',
|
|
106
|
+
value: path.resolve(cwd, target)
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error(`Unsupported Claude session target: ${target}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createClaudeHistoryAdapter() {
|
|
115
|
+
return {
|
|
116
|
+
id: 'claude-history',
|
|
117
|
+
description: 'Claude local session history and session-file snapshots',
|
|
118
|
+
targetTypes: ['claude-history', 'claude-alias', 'session-file'],
|
|
119
|
+
canOpen(target, context = {}) {
|
|
120
|
+
if (context.adapterId && context.adapterId !== 'claude-history') {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (context.adapterId === 'claude-history') {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const cwd = context.cwd || process.cwd();
|
|
129
|
+
return parseClaudeTarget(target) !== null || isSessionFileTarget(target, cwd);
|
|
130
|
+
},
|
|
131
|
+
open(target, context = {}) {
|
|
132
|
+
const cwd = context.cwd || process.cwd();
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
adapterId: 'claude-history',
|
|
136
|
+
getSnapshot() {
|
|
137
|
+
const { session, sourceTarget } = resolveSessionRecord(target, cwd);
|
|
138
|
+
return normalizeClaudeHistorySession(session, sourceTarget);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
createClaudeHistoryAdapter,
|
|
147
|
+
isSessionFileTarget,
|
|
148
|
+
parseClaudeTarget
|
|
149
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const { collectSessionSnapshot } = require('../orchestration-session');
|
|
7
|
+
const { normalizeDmuxSnapshot } = require('./canonical-session');
|
|
8
|
+
|
|
9
|
+
function isPlanFileTarget(target, cwd) {
|
|
10
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const absoluteTarget = path.resolve(cwd, target);
|
|
15
|
+
return fs.existsSync(absoluteTarget)
|
|
16
|
+
&& fs.statSync(absoluteTarget).isFile()
|
|
17
|
+
&& path.extname(absoluteTarget) === '.json';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isSessionNameTarget(target, cwd) {
|
|
21
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const coordinationDir = path.resolve(cwd, '.claude', 'orchestration', target);
|
|
26
|
+
return fs.existsSync(coordinationDir) && fs.statSync(coordinationDir).isDirectory();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildSourceTarget(target, cwd) {
|
|
30
|
+
if (isPlanFileTarget(target, cwd)) {
|
|
31
|
+
return {
|
|
32
|
+
type: 'plan',
|
|
33
|
+
value: path.resolve(cwd, target)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
type: 'session',
|
|
39
|
+
value: target
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createDmuxTmuxAdapter(options = {}) {
|
|
44
|
+
const collectSessionSnapshotImpl = options.collectSessionSnapshotImpl || collectSessionSnapshot;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
id: 'dmux-tmux',
|
|
48
|
+
description: 'Tmux/worktree orchestration snapshots from plan files or session names',
|
|
49
|
+
targetTypes: ['plan', 'session'],
|
|
50
|
+
canOpen(target, context = {}) {
|
|
51
|
+
if (context.adapterId && context.adapterId !== 'dmux-tmux') {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (context.adapterId === 'dmux-tmux') {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cwd = context.cwd || process.cwd();
|
|
60
|
+
return isPlanFileTarget(target, cwd) || isSessionNameTarget(target, cwd);
|
|
61
|
+
},
|
|
62
|
+
open(target, context = {}) {
|
|
63
|
+
const cwd = context.cwd || process.cwd();
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
adapterId: 'dmux-tmux',
|
|
67
|
+
getSnapshot() {
|
|
68
|
+
const snapshot = collectSessionSnapshotImpl(target, cwd);
|
|
69
|
+
return normalizeDmuxSnapshot(snapshot, buildSourceTarget(target, cwd));
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
createDmuxTmuxAdapter,
|
|
78
|
+
isPlanFileTarget,
|
|
79
|
+
isSessionNameTarget
|
|
80
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createClaudeHistoryAdapter } = require('./claude-history');
|
|
4
|
+
const { createDmuxTmuxAdapter } = require('./dmux-tmux');
|
|
5
|
+
|
|
6
|
+
const TARGET_TYPE_TO_ADAPTER_ID = Object.freeze({
|
|
7
|
+
plan: 'dmux-tmux',
|
|
8
|
+
session: 'dmux-tmux',
|
|
9
|
+
'claude-history': 'claude-history',
|
|
10
|
+
'claude-alias': 'claude-history',
|
|
11
|
+
'session-file': 'claude-history'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function createDefaultAdapters() {
|
|
15
|
+
return [
|
|
16
|
+
createClaudeHistoryAdapter(),
|
|
17
|
+
createDmuxTmuxAdapter()
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function coerceTargetValue(value) {
|
|
22
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
23
|
+
throw new Error('Structured session targets require a non-empty string value');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return value.trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeStructuredTarget(target, context = {}) {
|
|
30
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) {
|
|
31
|
+
return {
|
|
32
|
+
target,
|
|
33
|
+
context: { ...context }
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const value = coerceTargetValue(target.value);
|
|
38
|
+
const type = typeof target.type === 'string' ? target.type.trim() : '';
|
|
39
|
+
if (type.length === 0) {
|
|
40
|
+
throw new Error('Structured session targets require a non-empty type');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const adapterId = target.adapterId || TARGET_TYPE_TO_ADAPTER_ID[type] || context.adapterId || null;
|
|
44
|
+
const nextContext = {
|
|
45
|
+
...context,
|
|
46
|
+
adapterId
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (type === 'claude-history' || type === 'claude-alias') {
|
|
50
|
+
return {
|
|
51
|
+
target: `claude:${value}`,
|
|
52
|
+
context: nextContext
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
target: value,
|
|
58
|
+
context: nextContext
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createAdapterRegistry(options = {}) {
|
|
63
|
+
const adapters = options.adapters || createDefaultAdapters();
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
adapters,
|
|
67
|
+
getAdapter(id) {
|
|
68
|
+
const adapter = adapters.find(candidate => candidate.id === id);
|
|
69
|
+
if (!adapter) {
|
|
70
|
+
throw new Error(`Unknown session adapter: ${id}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return adapter;
|
|
74
|
+
},
|
|
75
|
+
listAdapters() {
|
|
76
|
+
return adapters.map(adapter => ({
|
|
77
|
+
id: adapter.id,
|
|
78
|
+
description: adapter.description || '',
|
|
79
|
+
targetTypes: Array.isArray(adapter.targetTypes) ? [...adapter.targetTypes] : []
|
|
80
|
+
}));
|
|
81
|
+
},
|
|
82
|
+
select(target, context = {}) {
|
|
83
|
+
const normalized = normalizeStructuredTarget(target, context);
|
|
84
|
+
const adapter = normalized.context.adapterId
|
|
85
|
+
? this.getAdapter(normalized.context.adapterId)
|
|
86
|
+
: adapters.find(candidate => candidate.canOpen(normalized.target, normalized.context));
|
|
87
|
+
if (!adapter) {
|
|
88
|
+
throw new Error(`No session adapter matched target: ${target}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return adapter;
|
|
92
|
+
},
|
|
93
|
+
open(target, context = {}) {
|
|
94
|
+
const normalized = normalizeStructuredTarget(target, context);
|
|
95
|
+
const adapter = this.select(normalized.target, normalized.context);
|
|
96
|
+
return adapter.open(normalized.target, normalized.context);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function inspectSessionTarget(target, options = {}) {
|
|
102
|
+
const registry = createAdapterRegistry(options);
|
|
103
|
+
return registry.open(target, options).getSnapshot();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
createAdapterRegistry,
|
|
108
|
+
createDefaultAdapters,
|
|
109
|
+
inspectSessionTarget,
|
|
110
|
+
normalizeStructuredTarget
|
|
111
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Aliases Library for Claude Code.
|
|
3
|
+
* Manages named aliases for session files, stored in ~/.claude/session-aliases.json.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Internal alias storage entry */
|
|
7
|
+
export interface AliasEntry {
|
|
8
|
+
sessionPath: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
updatedAt?: string;
|
|
11
|
+
title: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Alias data structure stored on disk */
|
|
15
|
+
export interface AliasStore {
|
|
16
|
+
version: string;
|
|
17
|
+
aliases: Record<string, AliasEntry>;
|
|
18
|
+
metadata: {
|
|
19
|
+
totalCount: number;
|
|
20
|
+
lastUpdated: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Resolved alias information returned by resolveAlias */
|
|
25
|
+
export interface ResolvedAlias {
|
|
26
|
+
alias: string;
|
|
27
|
+
sessionPath: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
title: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Alias entry returned by listAliases */
|
|
33
|
+
export interface AliasListItem {
|
|
34
|
+
name: string;
|
|
35
|
+
sessionPath: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt?: string;
|
|
38
|
+
title: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Result from mutation operations (set, delete, rename, update, cleanup) */
|
|
42
|
+
export interface AliasResult {
|
|
43
|
+
success: boolean;
|
|
44
|
+
error?: string;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SetAliasResult extends AliasResult {
|
|
49
|
+
isNew?: boolean;
|
|
50
|
+
alias?: string;
|
|
51
|
+
sessionPath?: string;
|
|
52
|
+
title?: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface DeleteAliasResult extends AliasResult {
|
|
56
|
+
alias?: string;
|
|
57
|
+
deletedSessionPath?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RenameAliasResult extends AliasResult {
|
|
61
|
+
oldAlias?: string;
|
|
62
|
+
newAlias?: string;
|
|
63
|
+
sessionPath?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CleanupResult {
|
|
67
|
+
totalChecked: number;
|
|
68
|
+
removed: number;
|
|
69
|
+
removedAliases: Array<{ name: string; sessionPath: string }>;
|
|
70
|
+
error?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ListAliasesOptions {
|
|
74
|
+
/** Filter aliases by name or title (partial match, case-insensitive) */
|
|
75
|
+
search?: string | null;
|
|
76
|
+
/** Maximum number of aliases to return */
|
|
77
|
+
limit?: number | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Get the path to the aliases JSON file */
|
|
81
|
+
export function getAliasesPath(): string;
|
|
82
|
+
|
|
83
|
+
/** Load all aliases from disk. Returns default structure if file doesn't exist. */
|
|
84
|
+
export function loadAliases(): AliasStore;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Save aliases to disk with atomic write (temp file + rename).
|
|
88
|
+
* Creates backup before writing; restores on failure.
|
|
89
|
+
*/
|
|
90
|
+
export function saveAliases(aliases: AliasStore): boolean;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve an alias name to its session data.
|
|
94
|
+
* @returns Alias data, or null if not found or invalid name
|
|
95
|
+
*/
|
|
96
|
+
export function resolveAlias(alias: string): ResolvedAlias | null;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create or update an alias for a session.
|
|
100
|
+
* Alias names must be alphanumeric with dashes/underscores.
|
|
101
|
+
* Reserved names (list, help, remove, delete, create, set) are rejected.
|
|
102
|
+
*/
|
|
103
|
+
export function setAlias(alias: string, sessionPath: string, title?: string | null): SetAliasResult;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* List all aliases, optionally filtered and limited.
|
|
107
|
+
* Results are sorted by updated time (newest first).
|
|
108
|
+
*/
|
|
109
|
+
export function listAliases(options?: ListAliasesOptions): AliasListItem[];
|
|
110
|
+
|
|
111
|
+
/** Delete an alias by name */
|
|
112
|
+
export function deleteAlias(alias: string): DeleteAliasResult;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Rename an alias. Fails if old alias doesn't exist or new alias already exists.
|
|
116
|
+
* New alias name must be alphanumeric with dashes/underscores.
|
|
117
|
+
*/
|
|
118
|
+
export function renameAlias(oldAlias: string, newAlias: string): RenameAliasResult;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve an alias or pass through a session path.
|
|
122
|
+
* First tries to resolve as alias; if not found, returns the input as-is.
|
|
123
|
+
*/
|
|
124
|
+
export function resolveSessionAlias(aliasOrId: string): string;
|
|
125
|
+
|
|
126
|
+
/** Update the title of an existing alias. Pass null to clear. */
|
|
127
|
+
export function updateAliasTitle(alias: string, title: string | null): AliasResult;
|
|
128
|
+
|
|
129
|
+
/** Get all aliases that point to a specific session path */
|
|
130
|
+
export function getAliasesForSession(sessionPath: string): Array<{ name: string; createdAt: string; title: string | null }>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Remove aliases whose sessions no longer exist.
|
|
134
|
+
* @param sessionExists - Function that returns true if a session path is valid
|
|
135
|
+
*/
|
|
136
|
+
export function cleanupAliases(sessionExists: (sessionPath: string) => boolean): CleanupResult;
|