@magclaw/cli-core 0.1.37 → 0.1.38
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/package.json +1 -1
- package/src/cli.js +607 -0
- package/src/team-memory-hooks.js +319 -0
- package/src/team-sharing.js +663 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -8,6 +8,55 @@ import os from 'node:os';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { renderListProfiles, shouldUseColor } from './list-renderer.js';
|
|
11
|
+
import {
|
|
12
|
+
buildTeamMemorySyncPackageFromTranscript,
|
|
13
|
+
installTeamMemoryHookConfig,
|
|
14
|
+
parseTeamMemoryTranscript,
|
|
15
|
+
} from './team-memory-hooks.js';
|
|
16
|
+
import {
|
|
17
|
+
checkTeamSharingUpgrade,
|
|
18
|
+
convertTeamSharingProjectToMemoryConfig,
|
|
19
|
+
disableTeamSharingSkill,
|
|
20
|
+
initTeamSharingProject,
|
|
21
|
+
installTeamSharingHooks,
|
|
22
|
+
installTeamSharingSkill,
|
|
23
|
+
listTeamSharingProjects,
|
|
24
|
+
loginTeamSharingProfile,
|
|
25
|
+
logoutTeamSharingProfile,
|
|
26
|
+
readTeamSharingProfileConfig,
|
|
27
|
+
readTeamSharingProjectConfig,
|
|
28
|
+
removeTeamSharingHooks,
|
|
29
|
+
removeTeamSharingSkill,
|
|
30
|
+
setTeamSharingProjectEnabled,
|
|
31
|
+
setupTeamSharing,
|
|
32
|
+
statusTeamSharingProject,
|
|
33
|
+
statusTeamSharingHooks,
|
|
34
|
+
statusTeamSharingSkill,
|
|
35
|
+
teamSharingPaths,
|
|
36
|
+
unsetTeamSharingProject,
|
|
37
|
+
whoamiTeamSharingProfile,
|
|
38
|
+
} from './team-sharing.js';
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
checkTeamSharingUpgrade,
|
|
42
|
+
disableTeamSharingSkill,
|
|
43
|
+
initTeamSharingProject,
|
|
44
|
+
installTeamSharingHooks,
|
|
45
|
+
installTeamSharingSkill,
|
|
46
|
+
listTeamSharingProjects,
|
|
47
|
+
loginTeamSharingProfile,
|
|
48
|
+
logoutTeamSharingProfile,
|
|
49
|
+
removeTeamSharingHooks,
|
|
50
|
+
removeTeamSharingSkill,
|
|
51
|
+
setTeamSharingProjectEnabled,
|
|
52
|
+
setupTeamSharing,
|
|
53
|
+
statusTeamSharingProject,
|
|
54
|
+
statusTeamSharingHooks,
|
|
55
|
+
statusTeamSharingSkill,
|
|
56
|
+
teamSharingPaths,
|
|
57
|
+
unsetTeamSharingProject,
|
|
58
|
+
whoamiTeamSharingProfile,
|
|
59
|
+
} from './team-sharing.js';
|
|
11
60
|
|
|
12
61
|
export const DEFAULT_PROFILE = 'default';
|
|
13
62
|
export const DEFAULT_SERVER_URL = 'http://127.0.0.1:6543';
|
|
@@ -694,6 +743,10 @@ function renderHelp() {
|
|
|
694
743
|
' list List local daemon profiles and connected Computers',
|
|
695
744
|
' logs Print recent daemon logs for one profile',
|
|
696
745
|
' install-cli Install or repair durable magclaw command shims',
|
|
746
|
+
' memory Configure and use MagClaw team-memory sync',
|
|
747
|
+
' team-sharing Configure MagClaw Team Sharing setup, login, hooks, and skill',
|
|
748
|
+
' skills Install or manage MagClaw feature skills',
|
|
749
|
+
' hooks Install or manage MagClaw feature hooks',
|
|
697
750
|
' upgrade Upgrade the background daemon package',
|
|
698
751
|
' doctor Show runtime and environment diagnostics',
|
|
699
752
|
' uninstall Stop and remove the background daemon service',
|
|
@@ -883,6 +936,85 @@ async function writeJsonFile(file, value) {
|
|
|
883
936
|
await writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
884
937
|
}
|
|
885
938
|
|
|
939
|
+
export function teamMemoryPaths({ profile = DEFAULT_PROFILE, cwd = process.cwd(), env = process.env } = {}) {
|
|
940
|
+
const home = homeDirForEnv(env) || os.homedir();
|
|
941
|
+
const memoryHome = path.resolve(env.MAGCLAW_MEMORY_HOME || path.join(home, '.magclaw', 'memory'));
|
|
942
|
+
const cleanProfile = safeProfileName(profile || env.MAGCLAW_MEMORY_PROFILE || DEFAULT_PROFILE);
|
|
943
|
+
const projectDir = path.resolve(cwd || process.cwd());
|
|
944
|
+
return {
|
|
945
|
+
profile: cleanProfile,
|
|
946
|
+
memoryHome,
|
|
947
|
+
profilesDir: path.join(memoryHome, 'profiles'),
|
|
948
|
+
profileConfig: path.join(memoryHome, 'profiles', cleanProfile, 'config.json'),
|
|
949
|
+
projectConfig: path.join(projectDir, '.magclaw', 'team-memory.json'),
|
|
950
|
+
projectCursor: path.join(projectDir, '.magclaw', 'team-memory-cursor.json'),
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function normalizeMemoryServerUrl(value = '') {
|
|
955
|
+
return String(value || DEFAULT_SERVER_URL).replace(/\/+$/, '');
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
export async function loginTeamMemoryProfile(flags = {}, env = process.env) {
|
|
959
|
+
const paths = teamMemoryPaths({ profile: flags.profile || env.MAGCLAW_MEMORY_PROFILE || DEFAULT_PROFILE, env });
|
|
960
|
+
const existing = await readJsonFile(paths.profileConfig, {});
|
|
961
|
+
const token = String(flags.token || flags.apiKey || flags.memoryToken || env.MAGCLAW_MEMORY_TOKEN || existing.token || '').trim();
|
|
962
|
+
const serverUrl = normalizeMemoryServerUrl(flags.serverUrl || existing.serverUrl || env.MAGCLAW_PUBLIC_URL || DEFAULT_SERVER_URL);
|
|
963
|
+
const profile = {
|
|
964
|
+
version: 1,
|
|
965
|
+
profile: paths.profile,
|
|
966
|
+
serverUrl,
|
|
967
|
+
workspaceId: String(flags.workspaceId || flags.workspace || existing.workspaceId || env.MAGCLAW_WORKSPACE_ID || 'local').trim(),
|
|
968
|
+
token,
|
|
969
|
+
tokenScope: ['team_memory:sync', 'team_memory:search', 'team_memory:context', 'team_memory:feedback'],
|
|
970
|
+
updatedAt: now(),
|
|
971
|
+
createdAt: existing.createdAt || now(),
|
|
972
|
+
};
|
|
973
|
+
await writeJsonFile(paths.profileConfig, profile);
|
|
974
|
+
return {
|
|
975
|
+
ok: true,
|
|
976
|
+
profile: paths.profile,
|
|
977
|
+
serverUrl,
|
|
978
|
+
workspaceId: profile.workspaceId,
|
|
979
|
+
hasToken: Boolean(token),
|
|
980
|
+
profileConfig: paths.profileConfig,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
export async function initTeamMemoryProject(flags = {}, env = process.env) {
|
|
985
|
+
const cwd = path.resolve(flags.cwd || process.cwd());
|
|
986
|
+
const paths = teamMemoryPaths({ profile: flags.profile || env.MAGCLAW_MEMORY_PROFILE || DEFAULT_PROFILE, cwd, env });
|
|
987
|
+
const channel = String(flags.channel || flags.channelId || flags.channelPath || flags._?.[1] || '').trim();
|
|
988
|
+
if (!channel) throw new Error('Usage: magclaw memory init --channel <channelPathOrId>');
|
|
989
|
+
const existing = await readJsonFile(paths.projectConfig, {});
|
|
990
|
+
const projectKey = String(flags.projectKey || existing.projectKey || path.basename(cwd)).trim();
|
|
991
|
+
const config = {
|
|
992
|
+
version: 1,
|
|
993
|
+
enabled: flags.enabled === undefined ? true : !['0', 'false', 'no'].includes(String(flags.enabled).toLowerCase()),
|
|
994
|
+
profile: paths.profile,
|
|
995
|
+
serverUrl: normalizeMemoryServerUrl(flags.serverUrl || existing.serverUrl || env.MAGCLAW_PUBLIC_URL || DEFAULT_SERVER_URL),
|
|
996
|
+
workspaceId: String(flags.workspaceId || flags.workspace || existing.workspaceId || env.MAGCLAW_WORKSPACE_ID || 'local').trim(),
|
|
997
|
+
channelId: String(flags.channelId || (!/^(https?|feishu|lark|mc):/i.test(channel) ? channel : existing.channelId || '')).trim(),
|
|
998
|
+
channelPath: String(flags.channelPath || (/^(https?|feishu|lark|mc):/i.test(channel) ? channel : existing.channelPath || '')).trim(),
|
|
999
|
+
routingMode: 'fixed_single_channel',
|
|
1000
|
+
projectKey,
|
|
1001
|
+
enabledRuntimes: ['codex', 'claude_code'],
|
|
1002
|
+
updatedAt: now(),
|
|
1003
|
+
createdAt: existing.createdAt || now(),
|
|
1004
|
+
};
|
|
1005
|
+
await writeJsonFile(paths.projectConfig, config);
|
|
1006
|
+
return {
|
|
1007
|
+
ok: true,
|
|
1008
|
+
projectConfig: paths.projectConfig,
|
|
1009
|
+
profile: paths.profile,
|
|
1010
|
+
serverUrl: config.serverUrl,
|
|
1011
|
+
workspaceId: config.workspaceId,
|
|
1012
|
+
channelId: config.channelId,
|
|
1013
|
+
channelPath: config.channelPath,
|
|
1014
|
+
projectKey,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
886
1018
|
async function readProfile(profile, env = process.env) {
|
|
887
1019
|
const paths = profilePaths(profile, env);
|
|
888
1020
|
const config = await readJsonFile(paths.config, {});
|
|
@@ -6567,6 +6699,469 @@ async function postSetupJson(serverUrl, pathname, body = {}) {
|
|
|
6567
6699
|
return data;
|
|
6568
6700
|
}
|
|
6569
6701
|
|
|
6702
|
+
async function teamMemoryRequestJson({ serverUrl, token = '', method = 'GET', pathname = '/api/team-memory/doctor', body = null } = {}) {
|
|
6703
|
+
const response = await fetch(`${normalizeMemoryServerUrl(serverUrl)}${pathname}`, {
|
|
6704
|
+
method,
|
|
6705
|
+
headers: {
|
|
6706
|
+
...(body ? { 'content-type': 'application/json' } : {}),
|
|
6707
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
6708
|
+
},
|
|
6709
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
6710
|
+
});
|
|
6711
|
+
const data = await response.json().catch(() => ({}));
|
|
6712
|
+
if (!response.ok) {
|
|
6713
|
+
throw new Error(data.error || data.message || `${response.status} ${response.statusText}`);
|
|
6714
|
+
}
|
|
6715
|
+
return data;
|
|
6716
|
+
}
|
|
6717
|
+
|
|
6718
|
+
async function readTeamMemoryProjectConfig(flags = {}, env = process.env) {
|
|
6719
|
+
const paths = teamMemoryPaths({ profile: flags.profile || env.MAGCLAW_MEMORY_PROFILE || DEFAULT_PROFILE, cwd: flags.cwd || process.cwd(), env });
|
|
6720
|
+
const teamSharing = await readTeamSharingProjectConfig({ profile: flags.profile || env.MAGCLAW_TEAM_SHARING_PROFILE || env.MAGCLAW_MEMORY_PROFILE || DEFAULT_PROFILE, cwd: flags.cwd || process.cwd(), env });
|
|
6721
|
+
if (teamSharing.config) {
|
|
6722
|
+
return {
|
|
6723
|
+
paths: {
|
|
6724
|
+
...paths,
|
|
6725
|
+
teamSharingProjectConfig: teamSharing.paths.projectConfig,
|
|
6726
|
+
},
|
|
6727
|
+
config: convertTeamSharingProjectToMemoryConfig(teamSharing.config),
|
|
6728
|
+
};
|
|
6729
|
+
}
|
|
6730
|
+
return {
|
|
6731
|
+
paths,
|
|
6732
|
+
config: await readJsonFile(paths.projectConfig, null),
|
|
6733
|
+
};
|
|
6734
|
+
}
|
|
6735
|
+
|
|
6736
|
+
async function readTeamMemoryProfile(profile, env = process.env) {
|
|
6737
|
+
const paths = teamMemoryPaths({ profile, env });
|
|
6738
|
+
const sharing = await readTeamSharingProfileConfig(profile, env);
|
|
6739
|
+
if (sharing.config?.token || sharing.config?.server_url) {
|
|
6740
|
+
return {
|
|
6741
|
+
paths: {
|
|
6742
|
+
...paths,
|
|
6743
|
+
teamSharingProfileConfig: sharing.paths.profileConfig,
|
|
6744
|
+
},
|
|
6745
|
+
config: {
|
|
6746
|
+
version: sharing.config.version || 1,
|
|
6747
|
+
profile: sharing.config.profile || profile,
|
|
6748
|
+
serverUrl: sharing.config.server_url || sharing.config.serverUrl,
|
|
6749
|
+
workspaceId: sharing.config.workspace_id || sharing.config.workspaceId,
|
|
6750
|
+
token: sharing.config.token,
|
|
6751
|
+
},
|
|
6752
|
+
};
|
|
6753
|
+
}
|
|
6754
|
+
return {
|
|
6755
|
+
paths,
|
|
6756
|
+
config: await readJsonFile(paths.profileConfig, {}),
|
|
6757
|
+
};
|
|
6758
|
+
}
|
|
6759
|
+
|
|
6760
|
+
async function resolveTeamMemoryClient(flags = {}, env = process.env) {
|
|
6761
|
+
const project = await readTeamMemoryProjectConfig(flags, env);
|
|
6762
|
+
if (!project.config) throw new Error('Run `magclaw memory init --channel <channel>` in this project first.');
|
|
6763
|
+
const profile = await readTeamMemoryProfile(flags.profile || project.config.profile || DEFAULT_PROFILE, env);
|
|
6764
|
+
return {
|
|
6765
|
+
project,
|
|
6766
|
+
profile,
|
|
6767
|
+
serverUrl: flags.serverUrl || project.config.serverUrl || profile.config.serverUrl || DEFAULT_SERVER_URL,
|
|
6768
|
+
token: String(profile.config.token || env.MAGCLAW_MEMORY_TOKEN || '').trim(),
|
|
6769
|
+
};
|
|
6770
|
+
}
|
|
6771
|
+
|
|
6772
|
+
async function doctorTeamMemory(flags = {}, env = process.env) {
|
|
6773
|
+
const project = await readTeamMemoryProjectConfig(flags, env);
|
|
6774
|
+
const profileName = flags.profile || project.config?.profile || env.MAGCLAW_MEMORY_PROFILE || DEFAULT_PROFILE;
|
|
6775
|
+
const profile = await readTeamMemoryProfile(profileName, env);
|
|
6776
|
+
const serverUrl = normalizeMemoryServerUrl(flags.serverUrl || project.config?.serverUrl || profile.config.serverUrl || DEFAULT_SERVER_URL);
|
|
6777
|
+
const token = String(profile.config.token || env.MAGCLAW_MEMORY_TOKEN || '').trim();
|
|
6778
|
+
const local = {
|
|
6779
|
+
projectConfig: { exists: Boolean(project.config), path: project.paths.projectConfig },
|
|
6780
|
+
profileConfig: { exists: Boolean(profile.config?.profile), path: profile.paths.profileConfig },
|
|
6781
|
+
hasToken: Boolean(token),
|
|
6782
|
+
channelConfigured: Boolean(project.config?.channelId || project.config?.channelPath),
|
|
6783
|
+
};
|
|
6784
|
+
if (flags.offline) {
|
|
6785
|
+
return { ok: Object.values(local).every((item) => typeof item === 'boolean' ? item : item.exists !== false), local, remote: null };
|
|
6786
|
+
}
|
|
6787
|
+
try {
|
|
6788
|
+
const remote = await teamMemoryRequestJson({ serverUrl, token, pathname: '/api/team-memory/doctor' });
|
|
6789
|
+
return {
|
|
6790
|
+
ok: Boolean(local.projectConfig.exists && local.profileConfig.exists && local.channelConfigured && remote.ok),
|
|
6791
|
+
serverUrl,
|
|
6792
|
+
local,
|
|
6793
|
+
remote,
|
|
6794
|
+
};
|
|
6795
|
+
} catch (error) {
|
|
6796
|
+
return {
|
|
6797
|
+
ok: false,
|
|
6798
|
+
serverUrl,
|
|
6799
|
+
local,
|
|
6800
|
+
remote: { ok: false, error: error?.message || String(error) },
|
|
6801
|
+
};
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
|
|
6805
|
+
function cursorLastOrdinal(cursor = {}, runtime = 'codex', sessionId = '') {
|
|
6806
|
+
return Number(cursor?.sessions?.[runtime]?.[sessionId]?.lastOrdinal || 0);
|
|
6807
|
+
}
|
|
6808
|
+
|
|
6809
|
+
async function writeTeamMemoryCursor(file, runtime, cursor) {
|
|
6810
|
+
const existing = await readJsonFile(file, {});
|
|
6811
|
+
const sessions = existing.sessions && typeof existing.sessions === 'object' ? existing.sessions : {};
|
|
6812
|
+
sessions[runtime] = sessions[runtime] && typeof sessions[runtime] === 'object' ? sessions[runtime] : {};
|
|
6813
|
+
sessions[runtime][cursor.sessionId] = {
|
|
6814
|
+
...(sessions[runtime][cursor.sessionId] || {}),
|
|
6815
|
+
...cursor,
|
|
6816
|
+
};
|
|
6817
|
+
await writeJsonFile(file, {
|
|
6818
|
+
version: 1,
|
|
6819
|
+
sessions,
|
|
6820
|
+
updatedAt: now(),
|
|
6821
|
+
});
|
|
6822
|
+
}
|
|
6823
|
+
|
|
6824
|
+
export async function syncTeamMemoryTranscript(flags = {}, env = process.env) {
|
|
6825
|
+
const transcriptPath = String(flags.transcript || flags.file || flags._?.[1] || '').trim();
|
|
6826
|
+
if (!transcriptPath) {
|
|
6827
|
+
if (flags.hookEvent) return { ok: true, empty: true, reason: 'missing_transcript_path' };
|
|
6828
|
+
throw new Error('Usage: magclaw memory sync --transcript <path>');
|
|
6829
|
+
}
|
|
6830
|
+
const project = await readTeamMemoryProjectConfig(flags, env);
|
|
6831
|
+
if (!project.config) throw new Error('Run `magclaw memory init --channel <channel>` in this project first.');
|
|
6832
|
+
const runtime = String(flags.runtime || 'codex').trim().toLowerCase() === 'claude' || String(flags.runtime || '').trim().toLowerCase() === 'claude-code'
|
|
6833
|
+
? 'claude_code'
|
|
6834
|
+
: String(flags.runtime || 'codex').trim().toLowerCase();
|
|
6835
|
+
if (project.config.enabled === false) {
|
|
6836
|
+
if (flags.hookEvent) return { ok: true, empty: true, reason: 'project_disabled' };
|
|
6837
|
+
throw new Error('Team Sharing is disabled for this project.');
|
|
6838
|
+
}
|
|
6839
|
+
const runtimeConfig = project.config.runtimes?.[runtime];
|
|
6840
|
+
if (flags.hookEvent && runtimeConfig && runtimeConfig.hooksEnabled === false) {
|
|
6841
|
+
return { ok: true, empty: true, reason: 'runtime_hooks_disabled' };
|
|
6842
|
+
}
|
|
6843
|
+
const profile = await readTeamMemoryProfile(flags.profile || project.config.profile || DEFAULT_PROFILE, env);
|
|
6844
|
+
const token = String(profile.config.token || env.MAGCLAW_MEMORY_TOKEN || '').trim();
|
|
6845
|
+
const content = await readFile(path.resolve(transcriptPath), 'utf8');
|
|
6846
|
+
const parsed = parseTeamMemoryTranscript(content, {
|
|
6847
|
+
runtime,
|
|
6848
|
+
sessionId: flags.sessionId || '',
|
|
6849
|
+
title: flags.title || '',
|
|
6850
|
+
projectDir: flags.cwd || process.cwd(),
|
|
6851
|
+
});
|
|
6852
|
+
const cursor = await readJsonFile(project.paths.projectCursor, {});
|
|
6853
|
+
const lastOrdinal = Number(flags.full ? 0 : cursorLastOrdinal(cursor, parsed.runtime, parsed.sessionId));
|
|
6854
|
+
const syncPackage = buildTeamMemorySyncPackageFromTranscript(content, {
|
|
6855
|
+
runtime: parsed.runtime,
|
|
6856
|
+
sessionId: parsed.sessionId,
|
|
6857
|
+
title: flags.title || parsed.title || path.basename(transcriptPath),
|
|
6858
|
+
projectKey: project.config.projectKey,
|
|
6859
|
+
workspaceId: project.config.workspaceId,
|
|
6860
|
+
channelId: project.config.channelId,
|
|
6861
|
+
channelPath: project.config.channelPath,
|
|
6862
|
+
projectDir: flags.cwd || process.cwd(),
|
|
6863
|
+
lastOrdinal,
|
|
6864
|
+
minCreatedAt: project.config.enabledSince || '',
|
|
6865
|
+
});
|
|
6866
|
+
if (syncPackage.empty || !syncPackage.body) return { ok: true, empty: true, cursor: syncPackage.cursor };
|
|
6867
|
+
const result = await teamMemoryRequestJson({
|
|
6868
|
+
serverUrl: flags.serverUrl || project.config.serverUrl || profile.config.serverUrl || DEFAULT_SERVER_URL,
|
|
6869
|
+
token,
|
|
6870
|
+
method: 'POST',
|
|
6871
|
+
pathname: '/api/team-memory/sync',
|
|
6872
|
+
body: syncPackage.body,
|
|
6873
|
+
});
|
|
6874
|
+
if (result?.ok !== false) {
|
|
6875
|
+
await writeTeamMemoryCursor(project.paths.projectCursor, parsed.runtime, syncPackage.cursor);
|
|
6876
|
+
}
|
|
6877
|
+
return {
|
|
6878
|
+
...result,
|
|
6879
|
+
cursor: syncPackage.cursor,
|
|
6880
|
+
};
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6883
|
+
export async function installTeamMemoryHooks(flags = {}, env = process.env) {
|
|
6884
|
+
const home = homeDirForEnv(env) || os.homedir();
|
|
6885
|
+
const cwd = path.resolve(flags.cwd || process.cwd());
|
|
6886
|
+
const runtime = String(flags.runtime || 'all').trim().toLowerCase();
|
|
6887
|
+
const output = { ok: true };
|
|
6888
|
+
if (runtime === 'all' || runtime === 'codex') {
|
|
6889
|
+
output.codex = await installTeamMemoryHookConfig({
|
|
6890
|
+
runtime: 'codex',
|
|
6891
|
+
configPath: flags.codexConfig || path.join(home, '.codex', 'hooks.json'),
|
|
6892
|
+
projectDir: cwd,
|
|
6893
|
+
});
|
|
6894
|
+
}
|
|
6895
|
+
if (runtime === 'all' || runtime === 'claude' || runtime === 'claude_code' || runtime === 'claude-code') {
|
|
6896
|
+
output.claude = await installTeamMemoryHookConfig({
|
|
6897
|
+
runtime: 'claude_code',
|
|
6898
|
+
configPath: flags.claudeConfig || path.join(home, '.claude', 'settings.json'),
|
|
6899
|
+
projectDir: cwd,
|
|
6900
|
+
});
|
|
6901
|
+
}
|
|
6902
|
+
output.ok = Boolean((!output.codex || output.codex.ok) && (!output.claude || output.claude.ok));
|
|
6903
|
+
return output;
|
|
6904
|
+
}
|
|
6905
|
+
|
|
6906
|
+
export async function searchTeamMemory(flags = {}, env = process.env) {
|
|
6907
|
+
const query = String(flags.query || flags._?.slice(1).join(' ') || '').trim();
|
|
6908
|
+
if (!query) throw new Error('Usage: magclaw memory search --query <text>');
|
|
6909
|
+
const { project, serverUrl, token } = await resolveTeamMemoryClient(flags, env);
|
|
6910
|
+
return teamMemoryRequestJson({
|
|
6911
|
+
serverUrl,
|
|
6912
|
+
token,
|
|
6913
|
+
method: 'POST',
|
|
6914
|
+
pathname: '/api/team-memory/search',
|
|
6915
|
+
body: {
|
|
6916
|
+
query,
|
|
6917
|
+
channelId: flags.channelId || project.config.channelId || '',
|
|
6918
|
+
projectKey: flags.projectKey || project.config.projectKey || '',
|
|
6919
|
+
dateRange: flags.dateRange || null,
|
|
6920
|
+
candidateK: flags.candidateK || undefined,
|
|
6921
|
+
limit: flags.limit || 5,
|
|
6922
|
+
},
|
|
6923
|
+
});
|
|
6924
|
+
}
|
|
6925
|
+
|
|
6926
|
+
export async function readTeamMemoryContext(flags = {}, env = process.env) {
|
|
6927
|
+
const sessionId = String(flags.sessionId || flags.session || flags._?.[1] || '').trim();
|
|
6928
|
+
if (!sessionId) throw new Error('Usage: magclaw memory context --session-id <sessionId>');
|
|
6929
|
+
const { serverUrl, token } = await resolveTeamMemoryClient(flags, env);
|
|
6930
|
+
const params = new URLSearchParams();
|
|
6931
|
+
if (flags.anchorEventId || flags.anchor) params.set('anchorEventId', String(flags.anchorEventId || flags.anchor));
|
|
6932
|
+
if (flags.direction) params.set('direction', String(flags.direction));
|
|
6933
|
+
if (flags.limit) params.set('limit', String(flags.limit));
|
|
6934
|
+
const suffix = params.toString() ? `?${params.toString()}` : '';
|
|
6935
|
+
return teamMemoryRequestJson({
|
|
6936
|
+
serverUrl,
|
|
6937
|
+
token,
|
|
6938
|
+
pathname: `/api/team-memory/context/${encodeURIComponent(sessionId)}${suffix}`,
|
|
6939
|
+
});
|
|
6940
|
+
}
|
|
6941
|
+
|
|
6942
|
+
function teamMemorySkillMarkdown() {
|
|
6943
|
+
return [
|
|
6944
|
+
'---',
|
|
6945
|
+
'name: magclaw-team-memory',
|
|
6946
|
+
'description: Search and read MagClaw team memory shared from Codex and Claude Code sessions.',
|
|
6947
|
+
'---',
|
|
6948
|
+
'',
|
|
6949
|
+
'# MagClaw Team Memory',
|
|
6950
|
+
'',
|
|
6951
|
+
'Use this skill when the user asks what the team discussed, wants to align with another session, or needs original AI conversation context.',
|
|
6952
|
+
'',
|
|
6953
|
+
'## Workflow',
|
|
6954
|
+
'',
|
|
6955
|
+
'1. Run `magclaw memory search --query "<question>" --limit 5` from the project directory.',
|
|
6956
|
+
'2. Answer from the returned L0/L1 evidence when the user only needs a rough understanding.',
|
|
6957
|
+
'3. For deep follow-up, run `magclaw memory context --session-id <sessionId> --anchor-event-id <eventId> --direction around --limit 20`.',
|
|
6958
|
+
'4. Cite session titles, source refs, and context URLs from the command output.',
|
|
6959
|
+
'',
|
|
6960
|
+
'## Rules',
|
|
6961
|
+
'',
|
|
6962
|
+
'- Do not upload local secrets or raw tool output.',
|
|
6963
|
+
'- Prefer concise synthesis first, then pull original context only when needed.',
|
|
6964
|
+
'- If search returns low confidence or too few results, ask a narrower question or date range.',
|
|
6965
|
+
'',
|
|
6966
|
+
].join('\n');
|
|
6967
|
+
}
|
|
6968
|
+
|
|
6969
|
+
async function writeTeamMemorySkill(rootDir) {
|
|
6970
|
+
const skillDir = path.join(rootDir, 'skills', 'magclaw-team-memory');
|
|
6971
|
+
await mkdir(skillDir, { recursive: true });
|
|
6972
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
6973
|
+
await writeFile(skillPath, teamMemorySkillMarkdown());
|
|
6974
|
+
return skillPath;
|
|
6975
|
+
}
|
|
6976
|
+
|
|
6977
|
+
export async function installTeamMemorySkill(flags = {}, env = process.env) {
|
|
6978
|
+
const home = homeDirForEnv(env) || os.homedir();
|
|
6979
|
+
const target = String(flags.target || 'codex').trim().toLowerCase();
|
|
6980
|
+
const output = { ok: true, installed: [] };
|
|
6981
|
+
if (target === 'codex' || target === 'all') {
|
|
6982
|
+
const codexHome = path.resolve(env.CODEX_HOME || path.join(home, '.codex'));
|
|
6983
|
+
output.installed.push({ target: 'codex', path: await writeTeamMemorySkill(codexHome) });
|
|
6984
|
+
}
|
|
6985
|
+
if (target === 'claude' || target === 'claude_code' || target === 'claude-code' || target === 'all') {
|
|
6986
|
+
const claudeHome = path.resolve(env.CLAUDE_HOME || path.join(home, '.claude'));
|
|
6987
|
+
output.installed.push({ target: 'claude_code', path: await writeTeamMemorySkill(claudeHome) });
|
|
6988
|
+
}
|
|
6989
|
+
output.ok = output.installed.length > 0;
|
|
6990
|
+
return output;
|
|
6991
|
+
}
|
|
6992
|
+
|
|
6993
|
+
async function runTeamMemoryCommand(flags = {}, env = process.env) {
|
|
6994
|
+
const subcommand = String(flags._?.[0] || 'help').trim();
|
|
6995
|
+
if (subcommand === 'help' || flags.help) {
|
|
6996
|
+
process.stdout.write([
|
|
6997
|
+
'Usage: magclaw memory <command> [options]',
|
|
6998
|
+
'',
|
|
6999
|
+
'Commands:',
|
|
7000
|
+
' login Save a scoped team-memory token in the user profile',
|
|
7001
|
+
' init Write .magclaw/team-memory.json for the current project',
|
|
7002
|
+
' doctor Check local and server-side team-memory configuration',
|
|
7003
|
+
' install-hooks Install Codex/Claude hook commands for this project',
|
|
7004
|
+
' install-skill Install the MagClaw team-memory skill locally',
|
|
7005
|
+
' search Query shared team memory through /api/team-memory/search',
|
|
7006
|
+
' context Read original context around a session anchor',
|
|
7007
|
+
' sync Upload one transcript file through /api/team-memory/sync',
|
|
7008
|
+
'',
|
|
7009
|
+
].join('\n'));
|
|
7010
|
+
return;
|
|
7011
|
+
}
|
|
7012
|
+
switch (subcommand) {
|
|
7013
|
+
case 'login':
|
|
7014
|
+
printJson(await loginTeamMemoryProfile(flags, env));
|
|
7015
|
+
break;
|
|
7016
|
+
case 'init':
|
|
7017
|
+
printJson(await initTeamMemoryProject(flags, env));
|
|
7018
|
+
break;
|
|
7019
|
+
case 'doctor':
|
|
7020
|
+
printJson(await doctorTeamMemory(flags, env));
|
|
7021
|
+
break;
|
|
7022
|
+
case 'install-hooks':
|
|
7023
|
+
case 'install':
|
|
7024
|
+
printJson(await installTeamMemoryHooks(flags, env));
|
|
7025
|
+
break;
|
|
7026
|
+
case 'install-skill':
|
|
7027
|
+
case 'skill':
|
|
7028
|
+
printJson(await installTeamMemorySkill(flags, env));
|
|
7029
|
+
break;
|
|
7030
|
+
case 'search':
|
|
7031
|
+
printJson(await searchTeamMemory(flags, env));
|
|
7032
|
+
break;
|
|
7033
|
+
case 'context':
|
|
7034
|
+
printJson(await readTeamMemoryContext(flags, env));
|
|
7035
|
+
break;
|
|
7036
|
+
case 'sync':
|
|
7037
|
+
printJson(await syncTeamMemoryTranscript(flags, env));
|
|
7038
|
+
break;
|
|
7039
|
+
default:
|
|
7040
|
+
throw new Error(`Unknown memory command: ${subcommand}`);
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
|
|
7044
|
+
async function runTeamSharingCommand(flags = {}, env = process.env) {
|
|
7045
|
+
const subcommand = String(flags._?.[0] || 'help').trim();
|
|
7046
|
+
if (subcommand === 'help' || flags.help) {
|
|
7047
|
+
process.stdout.write([
|
|
7048
|
+
'Usage: magclaw team-sharing <command> [options]',
|
|
7049
|
+
'',
|
|
7050
|
+
'Commands:',
|
|
7051
|
+
' setup Configure login, project channel, hooks, and skill',
|
|
7052
|
+
' login Browser/device login for scoped team-memory sync token',
|
|
7053
|
+
' logout Revoke and remove the cached Team Sharing token',
|
|
7054
|
+
' relogin Force a fresh browser/device login',
|
|
7055
|
+
' whoami Show the current Team Sharing identity',
|
|
7056
|
+
' projects List configured project paths',
|
|
7057
|
+
' init Write .magclaw/team-sharing.yaml for this project',
|
|
7058
|
+
' unset Remove this project Team Sharing config',
|
|
7059
|
+
' enable Enable this project sync',
|
|
7060
|
+
' disable Disable this project sync',
|
|
7061
|
+
' status Show project/login/hook/skill status',
|
|
7062
|
+
' doctor Check local config, server auth, hooks, skill, and upgrade state',
|
|
7063
|
+
' upgrade Check npm latest version for team-sharing',
|
|
7064
|
+
' search Query shared team memory',
|
|
7065
|
+
' context Read original context around an anchor',
|
|
7066
|
+
' sync Upload one transcript file',
|
|
7067
|
+
'',
|
|
7068
|
+
].join('\n'));
|
|
7069
|
+
return;
|
|
7070
|
+
}
|
|
7071
|
+
switch (subcommand) {
|
|
7072
|
+
case 'setup':
|
|
7073
|
+
case 'install':
|
|
7074
|
+
printJson({
|
|
7075
|
+
...(await setupTeamSharing(flags, env)),
|
|
7076
|
+
cli: flags.noInstallCli ? { ok: true, skipped: true } : await installCliShim(flags, env),
|
|
7077
|
+
});
|
|
7078
|
+
break;
|
|
7079
|
+
case 'login':
|
|
7080
|
+
printJson(await loginTeamSharingProfile(flags, env));
|
|
7081
|
+
break;
|
|
7082
|
+
case 'relogin':
|
|
7083
|
+
await logoutTeamSharingProfile(flags, env);
|
|
7084
|
+
printJson(await loginTeamSharingProfile(flags, env));
|
|
7085
|
+
break;
|
|
7086
|
+
case 'logout':
|
|
7087
|
+
printJson(await logoutTeamSharingProfile(flags, env));
|
|
7088
|
+
break;
|
|
7089
|
+
case 'whoami':
|
|
7090
|
+
printJson(await whoamiTeamSharingProfile(flags, env));
|
|
7091
|
+
break;
|
|
7092
|
+
case 'projects':
|
|
7093
|
+
printJson(await listTeamSharingProjects(flags, env));
|
|
7094
|
+
break;
|
|
7095
|
+
case 'init':
|
|
7096
|
+
printJson(await initTeamSharingProject(flags, env));
|
|
7097
|
+
break;
|
|
7098
|
+
case 'unset':
|
|
7099
|
+
printJson(await unsetTeamSharingProject(flags, env));
|
|
7100
|
+
break;
|
|
7101
|
+
case 'enable':
|
|
7102
|
+
printJson(await setTeamSharingProjectEnabled(flags, env, true));
|
|
7103
|
+
break;
|
|
7104
|
+
case 'disable':
|
|
7105
|
+
printJson(await setTeamSharingProjectEnabled(flags, env, false));
|
|
7106
|
+
break;
|
|
7107
|
+
case 'status':
|
|
7108
|
+
printJson({
|
|
7109
|
+
ok: true,
|
|
7110
|
+
project: await statusTeamSharingProject(flags, env),
|
|
7111
|
+
hooks: await statusTeamSharingHooks({ ...flags, target: flags.target || 'all' }, env),
|
|
7112
|
+
skill: await statusTeamSharingSkill({ ...flags, target: flags.target || 'all' }, env),
|
|
7113
|
+
});
|
|
7114
|
+
break;
|
|
7115
|
+
case 'doctor':
|
|
7116
|
+
printJson({
|
|
7117
|
+
ok: true,
|
|
7118
|
+
project: await statusTeamSharingProject(flags, env),
|
|
7119
|
+
hooks: await statusTeamSharingHooks({ ...flags, target: flags.target || 'all' }, env),
|
|
7120
|
+
skill: await statusTeamSharingSkill({ ...flags, target: flags.target || 'all' }, env),
|
|
7121
|
+
upgrade: await checkTeamSharingUpgrade({ force: Boolean(flags.force) }, env).catch((error) => ({ ok: false, error: error.message })),
|
|
7122
|
+
});
|
|
7123
|
+
break;
|
|
7124
|
+
case 'upgrade':
|
|
7125
|
+
printJson(await checkTeamSharingUpgrade({ force: true }, env));
|
|
7126
|
+
break;
|
|
7127
|
+
case 'search':
|
|
7128
|
+
printJson(await searchTeamMemory(flags, env));
|
|
7129
|
+
break;
|
|
7130
|
+
case 'context':
|
|
7131
|
+
printJson(await readTeamMemoryContext(flags, env));
|
|
7132
|
+
break;
|
|
7133
|
+
case 'sync':
|
|
7134
|
+
printJson(await syncTeamMemoryTranscript({ ...flags, integration: flags.integration || 'team-sharing' }, env));
|
|
7135
|
+
break;
|
|
7136
|
+
default:
|
|
7137
|
+
throw new Error(`Unknown team-sharing command: ${subcommand}`);
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
|
|
7141
|
+
async function runFeatureInstallCommand(kind, flags = {}, env = process.env) {
|
|
7142
|
+
const subcommand = String(flags._?.[0] || 'help').trim();
|
|
7143
|
+
const feature = String(flags.feature || flags.name || flags._?.[1] || 'team-sharing').trim();
|
|
7144
|
+
if (feature !== 'team-sharing' && feature !== 'team-memory') {
|
|
7145
|
+
throw new Error(`${kind} currently supports --feature team-sharing.`);
|
|
7146
|
+
}
|
|
7147
|
+
if (subcommand === 'help' || flags.help) {
|
|
7148
|
+
process.stdout.write(`Usage: magclaw ${kind} <install|remove|enable|disable|status> --feature team-sharing\n`);
|
|
7149
|
+
return;
|
|
7150
|
+
}
|
|
7151
|
+
if (kind === 'skills') {
|
|
7152
|
+
if (subcommand === 'install' || subcommand === 'enable') printJson(await installTeamSharingSkill(flags, env));
|
|
7153
|
+
else if (subcommand === 'remove') printJson(await removeTeamSharingSkill(flags, env));
|
|
7154
|
+
else if (subcommand === 'disable') printJson(await disableTeamSharingSkill(flags, env));
|
|
7155
|
+
else if (subcommand === 'status') printJson(await statusTeamSharingSkill(flags, env));
|
|
7156
|
+
else throw new Error(`Unknown skills command: ${subcommand}`);
|
|
7157
|
+
return;
|
|
7158
|
+
}
|
|
7159
|
+
if (subcommand === 'install' || subcommand === 'enable') printJson(await installTeamSharingHooks(flags, env));
|
|
7160
|
+
else if (subcommand === 'remove' || subcommand === 'disable') printJson(await removeTeamSharingHooks(flags, env));
|
|
7161
|
+
else if (subcommand === 'status') printJson(await statusTeamSharingHooks(flags, env));
|
|
7162
|
+
else throw new Error(`Unknown hooks command: ${subcommand}`);
|
|
7163
|
+
}
|
|
7164
|
+
|
|
6570
7165
|
function hasComputerTarget(flags = {}) {
|
|
6571
7166
|
return Boolean(flags.profileExplicit || flags.server || flags.serverSlug || flags.slug || flags._?.[1]);
|
|
6572
7167
|
}
|
|
@@ -7157,6 +7752,18 @@ export async function main(argv = process.argv, env = process.env) {
|
|
|
7157
7752
|
case 'computer':
|
|
7158
7753
|
await runComputerCommand(flags, env);
|
|
7159
7754
|
break;
|
|
7755
|
+
case 'memory':
|
|
7756
|
+
await runTeamMemoryCommand(flags, env);
|
|
7757
|
+
break;
|
|
7758
|
+
case 'team-sharing':
|
|
7759
|
+
await runTeamSharingCommand(flags, env);
|
|
7760
|
+
break;
|
|
7761
|
+
case 'skills':
|
|
7762
|
+
await runFeatureInstallCommand('skills', flags, env);
|
|
7763
|
+
break;
|
|
7764
|
+
case 'hooks':
|
|
7765
|
+
await runFeatureInstallCommand('hooks', flags, env);
|
|
7766
|
+
break;
|
|
7160
7767
|
case 'start': {
|
|
7161
7768
|
printJson(await startSavedBackground(flags, env));
|
|
7162
7769
|
break;
|