mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 10:18:14 +00:00
feat(auth): add opt-in shared context groups across accounts
This commit is contained in:
1 parent
1dae52014b
commit
4fed74c64b
21 files changed
+787
-25
No files matched your search
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Account context policy helpers.
|
||||
*
|
||||
* Controls whether account instances keep project context isolated, or share
|
||||
* project workspace context with other accounts in the same context group.
|
||||
*/
|
||||
|
||||
export type AccountContextMode = 'isolated' | 'shared';
|
||||
|
||||
export interface AccountContextMetadata {
|
||||
context_mode?: AccountContextMode;
|
||||
context_group?: string;
|
||||
}
|
||||
|
||||
export interface AccountContextPolicy {
|
||||
mode: AccountContextMode;
|
||||
group?: string;
|
||||
}
|
||||
|
||||
export interface CreateAccountContextInput {
|
||||
shareContext: boolean;
|
||||
contextGroup?: string;
|
||||
}
|
||||
|
||||
export interface ResolvedCreateAccountContext {
|
||||
policy: AccountContextPolicy;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_ACCOUNT_CONTEXT_MODE: AccountContextMode = 'isolated';
|
||||
export const DEFAULT_ACCOUNT_CONTEXT_GROUP = 'default';
|
||||
|
||||
const CONTEXT_GROUP_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
||||
|
||||
/**
|
||||
* Normalize context group names so paths and config stay consistent.
|
||||
*/
|
||||
export function normalizeContextGroupName(value: string): string {
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate context group naming constraints.
|
||||
*/
|
||||
export function isValidContextGroupName(value: string): boolean {
|
||||
return CONTEXT_GROUP_PATTERN.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime type guard for account context metadata payloads.
|
||||
*/
|
||||
export function isAccountContextMetadata(value: unknown): value is AccountContextMetadata {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidate = value as Record<string, unknown>;
|
||||
const mode = candidate['context_mode'];
|
||||
const group = candidate['context_group'];
|
||||
|
||||
const modeValid = mode === undefined || mode === 'isolated' || mode === 'shared';
|
||||
const groupValid = group === undefined || typeof group === 'string';
|
||||
|
||||
return modeValid && groupValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve create-command flags into a valid context policy.
|
||||
*/
|
||||
export function resolveCreateAccountContext(
|
||||
input: CreateAccountContextInput
|
||||
): ResolvedCreateAccountContext {
|
||||
const hasGroupFlag = input.contextGroup !== undefined;
|
||||
|
||||
if (hasGroupFlag) {
|
||||
if (!input.contextGroup || input.contextGroup.trim().length === 0) {
|
||||
return {
|
||||
policy: { mode: 'isolated' },
|
||||
error: 'Context group name is required after --context-group',
|
||||
};
|
||||
}
|
||||
|
||||
const normalizedGroup = normalizeContextGroupName(input.contextGroup);
|
||||
if (!isValidContextGroupName(normalizedGroup)) {
|
||||
return {
|
||||
policy: { mode: 'isolated' },
|
||||
error:
|
||||
'Invalid context group. Use letters/numbers/dash/underscore and start with a letter.',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
policy: {
|
||||
mode: 'shared',
|
||||
group: normalizedGroup,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (input.shareContext) {
|
||||
return {
|
||||
policy: {
|
||||
mode: 'shared',
|
||||
group: DEFAULT_ACCOUNT_CONTEXT_GROUP,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
policy: { mode: DEFAULT_ACCOUNT_CONTEXT_MODE },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve persisted metadata into runtime policy with safe defaults.
|
||||
*/
|
||||
export function resolveAccountContextPolicy(
|
||||
metadata?: AccountContextMetadata | null
|
||||
): AccountContextPolicy {
|
||||
const mode: AccountContextMode = metadata?.context_mode === 'shared' ? 'shared' : 'isolated';
|
||||
|
||||
if (mode === 'shared') {
|
||||
const rawGroup = metadata?.context_group;
|
||||
if (rawGroup && rawGroup.trim().length > 0) {
|
||||
const normalized = normalizeContextGroupName(rawGroup);
|
||||
if (isValidContextGroupName(normalized)) {
|
||||
return { mode: 'shared', group: normalized };
|
||||
}
|
||||
}
|
||||
|
||||
return { mode: 'shared', group: DEFAULT_ACCOUNT_CONTEXT_GROUP };
|
||||
}
|
||||
|
||||
return { mode: 'isolated' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert runtime policy back to persisted metadata.
|
||||
*/
|
||||
export function policyToAccountContextMetadata(
|
||||
policy: AccountContextPolicy
|
||||
): AccountContextMetadata {
|
||||
if (policy.mode === 'shared') {
|
||||
return {
|
||||
context_mode: 'shared',
|
||||
context_group: policy.group || DEFAULT_ACCOUNT_CONTEXT_GROUP,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
context_mode: 'isolated',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* User-facing summary for display/help output.
|
||||
*/
|
||||
export function formatAccountContextPolicy(policy: AccountContextPolicy): string {
|
||||
if (policy.mode === 'shared') {
|
||||
return `shared (${policy.group || DEFAULT_ACCOUNT_CONTEXT_GROUP})`;
|
||||
}
|
||||
|
||||
return 'isolated';
|
||||
}
|
||||
@@ -79,6 +79,12 @@ class AuthCommands {
|
||||
console.log(` ${dim('# Create & login to work profile')}`);
|
||||
console.log(` ${color('ccs auth create work', 'command')}`);
|
||||
console.log('');
|
||||
console.log(` ${dim('# Create account with shared project context (default group)')}`);
|
||||
console.log(` ${color('ccs auth create work2 --share-context', 'command')}`);
|
||||
console.log('');
|
||||
console.log(` ${dim('# Share context only within a specific group')}`);
|
||||
console.log(` ${color('ccs auth create backup --context-group sprint-a', 'command')}`);
|
||||
console.log('');
|
||||
console.log(` ${dim('# Set work as default')}`);
|
||||
console.log(` ${color('ccs auth default work', 'command')}`);
|
||||
console.log('');
|
||||
@@ -95,6 +101,12 @@ class AuthCommands {
|
||||
console.log(
|
||||
` ${color('--force', 'command')} Allow overwriting existing profile (create)`
|
||||
);
|
||||
console.log(
|
||||
` ${color('--share-context', 'command')} Share project workspace context across accounts`
|
||||
);
|
||||
console.log(
|
||||
` ${color('--context-group <name>', 'command')} Share context only within a named group`
|
||||
);
|
||||
console.log(
|
||||
` ${color('--yes, -y', 'command')} Skip confirmation prompts (remove)`
|
||||
);
|
||||
@@ -112,6 +124,9 @@ class AuthCommands {
|
||||
console.log(
|
||||
` Use ${color('ccs auth default <profile>', 'command')} to change the default profile.`
|
||||
);
|
||||
console.log(
|
||||
` Account profiles stay isolated unless you opt in with ${color('--share-context', 'command')}.`
|
||||
);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@ import { initUI, header, color, fail, warn, info, infoBox, warnBox } from '../..
|
||||
import { getClaudeCliInfo } from '../../utils/claude-detector';
|
||||
import { escapeShellArg, stripClaudeCodeEnv } from '../../utils/shell-executor';
|
||||
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||
import {
|
||||
resolveCreateAccountContext,
|
||||
policyToAccountContextMetadata,
|
||||
formatAccountContextPolicy,
|
||||
} from '../account-context';
|
||||
import { exitWithError } from '../../errors';
|
||||
import { ExitCode } from '../../errors/exit-codes';
|
||||
import { CommandContext, parseArgs } from './types';
|
||||
@@ -18,12 +23,14 @@ import { CommandContext, parseArgs } from './types';
|
||||
*/
|
||||
export async function handleCreate(ctx: CommandContext, args: string[]): Promise<void> {
|
||||
await initUI();
|
||||
const { profileName, force } = parseArgs(args);
|
||||
const { profileName, force, shareContext, contextGroup } = parseArgs(args);
|
||||
|
||||
if (!profileName) {
|
||||
console.log(fail('Profile name is required'));
|
||||
console.log('');
|
||||
console.log(`Usage: ${color('ccs auth create <profile> [--force]', 'command')}`);
|
||||
console.log(
|
||||
`Usage: ${color('ccs auth create <profile> [--force] [--share-context] [--context-group <name>]', 'command')}`
|
||||
);
|
||||
console.log('');
|
||||
console.log('Example:');
|
||||
console.log(` ${color('ccs auth create work', 'command')}`);
|
||||
@@ -39,28 +46,50 @@ export async function handleCreate(ctx: CommandContext, args: string[]): Promise
|
||||
exitWithError(`Profile already exists: ${profileName}`, ExitCode.PROFILE_ERROR);
|
||||
}
|
||||
|
||||
const resolvedContext = resolveCreateAccountContext({
|
||||
shareContext: !!shareContext,
|
||||
contextGroup,
|
||||
});
|
||||
|
||||
if (resolvedContext.error) {
|
||||
console.log(fail(resolvedContext.error));
|
||||
console.log('');
|
||||
exitWithError(resolvedContext.error, ExitCode.PROFILE_ERROR);
|
||||
}
|
||||
|
||||
const contextPolicy = resolvedContext.policy;
|
||||
const contextMetadata = policyToAccountContextMetadata(contextPolicy);
|
||||
|
||||
try {
|
||||
// Create instance directory
|
||||
console.log(info(`Creating profile: ${profileName}`));
|
||||
const instancePath = await ctx.instanceMgr.ensureInstance(profileName);
|
||||
const instancePath = await ctx.instanceMgr.ensureInstance(profileName, contextPolicy);
|
||||
|
||||
// Create/update profile entry based on config mode
|
||||
if (isUnifiedMode()) {
|
||||
// Use unified config (config.yaml)
|
||||
if (existsUnified) {
|
||||
ctx.registry.updateAccountUnified(profileName, {
|
||||
context_mode: contextMetadata.context_mode,
|
||||
context_group: contextMetadata.context_group,
|
||||
});
|
||||
ctx.registry.touchAccountUnified(profileName);
|
||||
} else {
|
||||
ctx.registry.createAccountUnified(profileName);
|
||||
ctx.registry.createAccountUnified(profileName, contextMetadata);
|
||||
}
|
||||
} else {
|
||||
// Use legacy profiles.json
|
||||
if (existsLegacy) {
|
||||
ctx.registry.updateProfile(profileName, {
|
||||
type: 'account',
|
||||
context_mode: contextMetadata.context_mode,
|
||||
context_group: contextMetadata.context_group,
|
||||
});
|
||||
} else {
|
||||
ctx.registry.createProfile(profileName, {
|
||||
type: 'account',
|
||||
context_mode: contextMetadata.context_mode,
|
||||
context_group: contextMetadata.context_group,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -108,7 +137,10 @@ export async function handleCreate(ctx: CommandContext, args: string[]): Promise
|
||||
console.log('');
|
||||
console.log(
|
||||
infoBox(
|
||||
`Profile: ${profileName}\n` + `Instance: ${instancePath}\n` + `Type: account`,
|
||||
`Profile: ${profileName}\n` +
|
||||
`Instance: ${instancePath}\n` +
|
||||
`Type: account\n` +
|
||||
`Context: ${formatAccountContextPolicy(contextPolicy)}`,
|
||||
'Profile Created'
|
||||
)
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { ProfileMetadata } from '../../types';
|
||||
import { initUI, header, color, dim, warn, table } from '../../utils/ui';
|
||||
import { resolveAccountContextPolicy, formatAccountContextPolicy } from '../account-context';
|
||||
import { exitWithError } from '../../errors';
|
||||
import { ExitCode } from '../../errors/exit-codes';
|
||||
import { CommandContext, ListOutput, parseArgs, formatRelativeTime } from './types';
|
||||
@@ -41,6 +42,7 @@ export async function handleList(ctx: CommandContext, args: string[]): Promise<v
|
||||
version: ctx.version,
|
||||
profiles: profileNames.map((name) => {
|
||||
const profile = profiles[name];
|
||||
const contextPolicy = resolveAccountContextPolicy(profile);
|
||||
const isDefault = name === defaultProfile;
|
||||
const instancePath = ctx.instanceMgr.getInstancePath(name);
|
||||
|
||||
@@ -50,6 +52,8 @@ export async function handleList(ctx: CommandContext, args: string[]): Promise<v
|
||||
is_default: isDefault,
|
||||
created: profile.created,
|
||||
last_used: profile.last_used || null,
|
||||
context_mode: contextPolicy.mode,
|
||||
context_group: contextPolicy.group || null,
|
||||
instance_path: instancePath,
|
||||
};
|
||||
}),
|
||||
@@ -98,6 +102,7 @@ export async function handleList(ctx: CommandContext, args: string[]): Promise<v
|
||||
const rows: string[][] = sorted.map((name) => {
|
||||
const profile = profiles[name];
|
||||
const isDefault = name === defaultProfile;
|
||||
const contextPolicy = resolveAccountContextPolicy(profile);
|
||||
|
||||
// Status column
|
||||
const status = isDefault ? color('[OK] default', 'success') : color('[OK]', 'success');
|
||||
@@ -112,6 +117,7 @@ export async function handleList(ctx: CommandContext, args: string[]): Promise<v
|
||||
|
||||
if (verbose) {
|
||||
row.push(lastUsed);
|
||||
row.push(formatAccountContextPolicy(contextPolicy));
|
||||
}
|
||||
|
||||
return row;
|
||||
@@ -119,14 +125,14 @@ export async function handleList(ctx: CommandContext, args: string[]): Promise<v
|
||||
|
||||
// Headers
|
||||
const headers = verbose
|
||||
? ['Profile', 'Type', 'Status', 'Last Used']
|
||||
? ['Profile', 'Type', 'Status', 'Last Used', 'Context']
|
||||
: ['Profile', 'Type', 'Status'];
|
||||
|
||||
// Print table
|
||||
console.log(
|
||||
table(rows, {
|
||||
head: headers,
|
||||
colWidths: verbose ? [15, 12, 15, 12] : [15, 12, 15],
|
||||
colWidths: verbose ? [15, 12, 15, 12, 18] : [15, 12, 15],
|
||||
})
|
||||
);
|
||||
console.log('');
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { initUI, header, color, fail, table } from '../../utils/ui';
|
||||
import { resolveAccountContextPolicy, formatAccountContextPolicy } from '../account-context';
|
||||
import { exitWithError } from '../../errors';
|
||||
import { ExitCode } from '../../errors/exit-codes';
|
||||
import { CommandContext, ProfileOutput, parseArgs } from './types';
|
||||
@@ -35,6 +36,7 @@ export async function handleShow(ctx: CommandContext, args: string[]): Promise<v
|
||||
const defaultProfile = ctx.registry.getDefaultResolved();
|
||||
const isDefault = profileName === defaultProfile;
|
||||
const instancePath = ctx.instanceMgr.getInstancePath(profileName);
|
||||
const contextPolicy = resolveAccountContextPolicy(profile);
|
||||
|
||||
// Count sessions
|
||||
let sessionCount = 0;
|
||||
@@ -56,6 +58,8 @@ export async function handleShow(ctx: CommandContext, args: string[]): Promise<v
|
||||
is_default: isDefault,
|
||||
created: profile.created,
|
||||
last_used: profile.last_used || null,
|
||||
context_mode: contextPolicy.mode,
|
||||
context_group: contextPolicy.group || null,
|
||||
instance_path: instancePath,
|
||||
session_count: sessionCount,
|
||||
};
|
||||
@@ -74,6 +78,7 @@ export async function handleShow(ctx: CommandContext, args: string[]): Promise<v
|
||||
['Instance', instancePath],
|
||||
['Created', new Date(profile.created).toLocaleString()],
|
||||
['Last Used', profile.last_used ? new Date(profile.last_used).toLocaleString() : 'Never'],
|
||||
['Context', formatAccountContextPolicy(contextPolicy)],
|
||||
['Sessions', `${sessionCount}`],
|
||||
];
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ export interface AuthCommandArgs {
|
||||
verbose?: boolean;
|
||||
json?: boolean;
|
||||
yes?: boolean;
|
||||
shareContext?: boolean;
|
||||
contextGroup?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,6 +32,8 @@ export interface ProfileOutput {
|
||||
is_default: boolean;
|
||||
created: string;
|
||||
last_used: string | null;
|
||||
context_mode?: 'isolated' | 'shared';
|
||||
context_group?: string | null;
|
||||
instance_path?: string;
|
||||
session_count?: number;
|
||||
}
|
||||
@@ -55,12 +59,45 @@ export interface CommandContext {
|
||||
* Parse command arguments from raw args array
|
||||
*/
|
||||
export function parseArgs(args: string[]): AuthCommandArgs {
|
||||
const profileName = args.find((arg) => !arg.startsWith('--'));
|
||||
let profileName: string | undefined;
|
||||
let contextGroup: string | undefined;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
|
||||
if (arg === '--context-group') {
|
||||
const next = args[i + 1];
|
||||
if (!next || next.startsWith('-')) {
|
||||
contextGroup = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
contextGroup = next;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith('--context-group=')) {
|
||||
contextGroup = arg.slice('--context-group='.length);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith('-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!profileName) {
|
||||
profileName = arg;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
profileName,
|
||||
force: args.includes('--force'),
|
||||
verbose: args.includes('--verbose'),
|
||||
json: args.includes('--json'),
|
||||
yes: args.includes('--yes') || args.includes('-y'),
|
||||
shareContext: args.includes('--share-context'),
|
||||
contextGroup,
|
||||
};
|
||||
}
|
||||
@@ -200,6 +200,8 @@ class ProfileDetector {
|
||||
type: 'account',
|
||||
created: account.created,
|
||||
last_used: account.last_used,
|
||||
context_mode: account.context_mode,
|
||||
context_group: account.context_group,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
saveUnifiedConfig,
|
||||
isUnifiedMode,
|
||||
} from '../config/unified-config-loader';
|
||||
import type { AccountConfig } from '../config/unified-config-types';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
/**
|
||||
@@ -19,6 +20,8 @@ import { getCcsDir } from '../utils/config-manager';
|
||||
* type: 'account', // Profile type
|
||||
* created: <ISO timestamp>, // Creation time
|
||||
* last_used: <ISO timestamp or null> // Last usage time
|
||||
* context_mode?: 'isolated' | 'shared' // Workspace context policy
|
||||
* context_group?: <string> // Shared context group when mode=shared
|
||||
* }
|
||||
*
|
||||
* Removed fields from v2.x:
|
||||
@@ -37,6 +40,8 @@ interface CreateMetadata {
|
||||
type?: string;
|
||||
created?: string;
|
||||
last_used?: string | null;
|
||||
context_mode?: 'isolated' | 'shared';
|
||||
context_group?: string;
|
||||
}
|
||||
|
||||
export class ProfileRegistry {
|
||||
@@ -46,6 +51,30 @@ export class ProfileRegistry {
|
||||
this.profilesPath = path.join(getCcsDir(), 'profiles.json');
|
||||
}
|
||||
|
||||
private normalizeLegacyProfileMetadata(metadata: ProfileMetadata): ProfileMetadata {
|
||||
const normalized: ProfileMetadata = { ...metadata };
|
||||
|
||||
if (normalized.context_mode !== 'shared') {
|
||||
delete normalized.context_group;
|
||||
} else if (!normalized.context_group || normalized.context_group.trim().length === 0) {
|
||||
delete normalized.context_group;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private normalizeUnifiedAccountConfig(account: AccountConfig): AccountConfig {
|
||||
const normalized: AccountConfig = { ...account };
|
||||
|
||||
if (normalized.context_mode !== 'shared') {
|
||||
delete normalized.context_group;
|
||||
} else if (!normalized.context_group || normalized.context_group.trim().length === 0) {
|
||||
delete normalized.context_group;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read profiles from disk
|
||||
*/
|
||||
@@ -105,11 +134,13 @@ export class ProfileRegistry {
|
||||
}
|
||||
|
||||
// v3.0 minimal schema: only essential fields
|
||||
data.profiles[name] = {
|
||||
data.profiles[name] = this.normalizeLegacyProfileMetadata({
|
||||
type: metadata.type || 'account',
|
||||
created: metadata.created || new Date().toISOString(),
|
||||
last_used: metadata.last_used || null,
|
||||
};
|
||||
context_mode: metadata.context_mode,
|
||||
context_group: metadata.context_group,
|
||||
});
|
||||
|
||||
// Note: No longer auto-set as default
|
||||
// Users must explicitly run: ccs auth default <profile>
|
||||
@@ -141,10 +172,10 @@ export class ProfileRegistry {
|
||||
throw new Error(`Profile not found: ${name}`);
|
||||
}
|
||||
|
||||
data.profiles[name] = {
|
||||
data.profiles[name] = this.normalizeLegacyProfileMetadata({
|
||||
...data.profiles[name],
|
||||
...updates,
|
||||
};
|
||||
});
|
||||
|
||||
this._write(data);
|
||||
}
|
||||
@@ -242,15 +273,32 @@ export class ProfileRegistry {
|
||||
/**
|
||||
* Create account in unified config (config.yaml)
|
||||
*/
|
||||
createAccountUnified(name: string): void {
|
||||
createAccountUnified(name: string, metadata: CreateMetadata = {}): void {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
if (config.accounts[name]) {
|
||||
throw new Error(`Account already exists: ${name}`);
|
||||
}
|
||||
config.accounts[name] = {
|
||||
config.accounts[name] = this.normalizeUnifiedAccountConfig({
|
||||
created: new Date().toISOString(),
|
||||
last_used: null,
|
||||
};
|
||||
context_mode: metadata.context_mode,
|
||||
context_group: metadata.context_group,
|
||||
});
|
||||
saveUnifiedConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update account metadata in unified config
|
||||
*/
|
||||
updateAccountUnified(name: string, updates: Partial<AccountConfig>): void {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
if (!config.accounts[name]) {
|
||||
throw new Error(`Account not found: ${name}`);
|
||||
}
|
||||
config.accounts[name] = this.normalizeUnifiedAccountConfig({
|
||||
...config.accounts[name],
|
||||
...updates,
|
||||
});
|
||||
saveUnifiedConfig(config);
|
||||
}
|
||||
|
||||
@@ -306,7 +354,7 @@ export class ProfileRegistry {
|
||||
/**
|
||||
* Get all accounts from unified config
|
||||
*/
|
||||
getAllAccountsUnified(): Record<string, { created: string; last_used: string | null }> {
|
||||
getAllAccountsUnified(): Record<string, AccountConfig> {
|
||||
if (!isUnifiedMode()) return {};
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
return config.accounts;
|
||||
@@ -330,6 +378,7 @@ export class ProfileRegistry {
|
||||
throw new Error(`Account not found: ${name}`);
|
||||
}
|
||||
config.accounts[name].last_used = new Date().toISOString();
|
||||
config.accounts[name] = this.normalizeUnifiedAccountConfig(config.accounts[name]);
|
||||
saveUnifiedConfig(config);
|
||||
}
|
||||
|
||||
@@ -355,6 +404,8 @@ export class ProfileRegistry {
|
||||
type: 'account',
|
||||
created: account.created,
|
||||
last_used: account.last_used,
|
||||
context_mode: account.context_mode,
|
||||
context_group: account.context_group,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user