@mod-computer/cli 0.2.3 → 0.2.5

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.
Files changed (75) hide show
  1. package/dist/cli.bundle.js +216 -36371
  2. package/package.json +3 -3
  3. package/dist/app.js +0 -227
  4. package/dist/cli.bundle.js.map +0 -7
  5. package/dist/cli.js +0 -132
  6. package/dist/commands/add.js +0 -245
  7. package/dist/commands/agents-run.js +0 -71
  8. package/dist/commands/auth.js +0 -259
  9. package/dist/commands/branch.js +0 -1411
  10. package/dist/commands/claude-sync.js +0 -772
  11. package/dist/commands/comment.js +0 -568
  12. package/dist/commands/diff.js +0 -182
  13. package/dist/commands/index.js +0 -73
  14. package/dist/commands/init.js +0 -597
  15. package/dist/commands/ls.js +0 -135
  16. package/dist/commands/members.js +0 -687
  17. package/dist/commands/mv.js +0 -282
  18. package/dist/commands/recover.js +0 -207
  19. package/dist/commands/rm.js +0 -257
  20. package/dist/commands/spec.js +0 -386
  21. package/dist/commands/status.js +0 -296
  22. package/dist/commands/sync.js +0 -119
  23. package/dist/commands/trace.js +0 -1752
  24. package/dist/commands/workspace.js +0 -447
  25. package/dist/components/conflict-resolution-ui.js +0 -120
  26. package/dist/components/messages.js +0 -5
  27. package/dist/components/thread.js +0 -8
  28. package/dist/config/features.js +0 -83
  29. package/dist/containers/branches-container.js +0 -140
  30. package/dist/containers/directory-container.js +0 -92
  31. package/dist/containers/thread-container.js +0 -214
  32. package/dist/containers/threads-container.js +0 -27
  33. package/dist/containers/workspaces-container.js +0 -27
  34. package/dist/daemon/conflict-resolution.js +0 -172
  35. package/dist/daemon/content-hash.js +0 -31
  36. package/dist/daemon/file-sync.js +0 -985
  37. package/dist/daemon/index.js +0 -203
  38. package/dist/daemon/mime-types.js +0 -166
  39. package/dist/daemon/offline-queue.js +0 -211
  40. package/dist/daemon/path-utils.js +0 -64
  41. package/dist/daemon/share-policy.js +0 -83
  42. package/dist/daemon/wasm-errors.js +0 -189
  43. package/dist/daemon/worker.js +0 -557
  44. package/dist/daemon-worker.js +0 -258
  45. package/dist/errors/workspace-errors.js +0 -48
  46. package/dist/lib/auth-server.js +0 -216
  47. package/dist/lib/browser.js +0 -35
  48. package/dist/lib/diff.js +0 -284
  49. package/dist/lib/formatters.js +0 -204
  50. package/dist/lib/git.js +0 -137
  51. package/dist/lib/local-fs.js +0 -201
  52. package/dist/lib/prompts.js +0 -56
  53. package/dist/lib/storage.js +0 -213
  54. package/dist/lib/trace-formatters.js +0 -314
  55. package/dist/services/add-service.js +0 -554
  56. package/dist/services/add-validation.js +0 -124
  57. package/dist/services/automatic-file-tracker.js +0 -303
  58. package/dist/services/cli-orchestrator.js +0 -227
  59. package/dist/services/feature-flags.js +0 -187
  60. package/dist/services/file-import-service.js +0 -283
  61. package/dist/services/file-transformation-service.js +0 -218
  62. package/dist/services/logger.js +0 -44
  63. package/dist/services/mod-config.js +0 -67
  64. package/dist/services/modignore-service.js +0 -328
  65. package/dist/services/sync-daemon.js +0 -244
  66. package/dist/services/thread-notification-service.js +0 -50
  67. package/dist/services/thread-service.js +0 -147
  68. package/dist/stores/use-directory-store.js +0 -96
  69. package/dist/stores/use-threads-store.js +0 -46
  70. package/dist/stores/use-workspaces-store.js +0 -54
  71. package/dist/types/add-types.js +0 -99
  72. package/dist/types/config.js +0 -16
  73. package/dist/types/index.js +0 -2
  74. package/dist/types/workspace-connection.js +0 -53
  75. package/dist/types.js +0 -1
@@ -1,50 +0,0 @@
1
- export class ThreadNotificationService {
2
- constructor(repo) {
3
- this.repo = repo;
4
- }
5
- /**
6
- * Add a file update notification to the thread
7
- * For now, this just logs the notification - can be enhanced later
8
- */
9
- async addFileUpdateNotification(threadId, fileName, action) {
10
- try {
11
- const message = this.getNotificationMessage(fileName, action);
12
- // For now, just log the notification
13
- // TODO: Later integrate with actual thread message system
14
- console.log(`📢 ${message}`);
15
- }
16
- catch (error) {
17
- console.error(`Failed to add file update notification:`, error);
18
- }
19
- }
20
- /**
21
- * Generate notification message based on action
22
- */
23
- getNotificationMessage(fileName, action) {
24
- const emoji = this.getActionEmoji(action);
25
- const verb = this.getActionVerb(action);
26
- return `${emoji} ${fileName} ${verb}`;
27
- }
28
- /**
29
- * Get emoji for action type
30
- */
31
- getActionEmoji(action) {
32
- switch (action) {
33
- case 'updated': return '📝';
34
- case 'created': return '✨';
35
- case 'deleted': return '🗑️';
36
- default: return '📄';
37
- }
38
- }
39
- /**
40
- * Get verb for action type
41
- */
42
- getActionVerb(action) {
43
- switch (action) {
44
- case 'updated': return 'updated';
45
- case 'created': return 'created';
46
- case 'deleted': return 'deleted';
47
- default: return 'changed';
48
- }
49
- }
50
- }
@@ -1,147 +0,0 @@
1
- import { ThreadService as CoreThreadService } from '@mod/mod-core/services/thread-service';
2
- import { BranchService } from '@mod/mod-core/services/branch-service';
3
- import { chatWithAgentCli } from './cli-orchestrator.js';
4
- import { log } from './logger.js';
5
- export class CLIThreadService {
6
- // private ai: typeof runAgentWithStream; // Placeholder for orchestrator/agent logic
7
- constructor(repo) {
8
- this.core = new CoreThreadService(repo);
9
- this.repo = repo;
10
- // this.ai = runAgentWithStream; // Assign orchestrator/agent logic as needed
11
- }
12
- async getThreads(workspaceId) {
13
- const wsHandle = await this.repo.find(workspaceId);
14
- const workspace = (await wsHandle.doc());
15
- if (!workspace || !workspace.branchesDocId)
16
- return [];
17
- const branchService = new BranchService(this.repo);
18
- const threads = [];
19
- try {
20
- const branches = await branchService.getBranchesForWorkspace(workspace.branchesDocId);
21
- for (const branch of branches) {
22
- const threadId = branch?.threadId;
23
- if (!threadId)
24
- continue;
25
- try {
26
- const thread = await this.core.getThread(threadId);
27
- if (thread)
28
- threads.push(thread);
29
- }
30
- catch (err) {
31
- log('[CLIThreadService] Failed to load thread', {
32
- branchId: branch?.id,
33
- threadId,
34
- err,
35
- });
36
- }
37
- }
38
- }
39
- catch (err) {
40
- log('[CLIThreadService] Failed to load branches for workspace', {
41
- workspaceId,
42
- err,
43
- });
44
- }
45
- return threads;
46
- }
47
- async getThread(threadId) {
48
- return this.core.getThread(threadId);
49
- }
50
- async createThread(name, branchId, workspaceId) {
51
- const threadId = await this.core.createThread(name, branchId);
52
- try {
53
- const branchService = new BranchService(this.repo);
54
- const wsHandle = await this.repo.find(workspaceId);
55
- const workspace = (await wsHandle.doc());
56
- if (workspace?.branchesDocId) {
57
- await branchService.setThreadId(branchId, threadId, workspace.branchesDocId);
58
- }
59
- wsHandle.change((doc) => {
60
- doc.activeThreadId = threadId;
61
- });
62
- }
63
- catch (err) {
64
- log('[CLIThreadService] Failed to persist threadId on branch', {
65
- branchId,
66
- workspaceId,
67
- err,
68
- });
69
- }
70
- return threadId;
71
- }
72
- /**
73
- * Stream agent chat for a thread in the CLI.
74
- * Yields agent response chunks for UI updates.
75
- */
76
- async *streamAgentChat({ threadId, userMessage, user = { id: 'user-1', name: 'You', avatarUrl: '' }, workspace, agent, files = [], images = [], maxMessages = 20, ...opts }) {
77
- // 1) Persist user message immediately
78
- await this.core.addThreadItemToThread(threadId, userMessage, user.id, user.name, user.avatarUrl, { workspaceId: workspace.id });
79
- // 2) Start CLI-native orchestrator stream
80
- const apiKey = (process.env.OPENAI_API_KEY || process.env.OPENAI_API_TOKEN || process.env.OPENAI || process.env.ANTHROPIC_API_KEY || '').trim();
81
- try {
82
- try {
83
- log('[CLIThreadService] Starting stream with agent:', {
84
- name: agent?.name,
85
- id: agent?.id,
86
- defaultModel: agent?.defaultModel,
87
- tools: agent?.tools ? Object.keys(agent.tools) : [],
88
- });
89
- }
90
- catch { }
91
- for await (const part of chatWithAgentCli({
92
- repo: this.repo,
93
- threadId,
94
- userMessage,
95
- user,
96
- workspace,
97
- agent: agent || { name: 'Assistant', instructions: 'Be helpful.' },
98
- files,
99
- images,
100
- apiKey,
101
- tools: (agent && agent.tools) || {},
102
- })) {
103
- const p = part;
104
- if (p.type === 'content' && p.content) {
105
- yield { type: 'content', content: p.content };
106
- }
107
- else if (p.type === 'tool-call') {
108
- yield p;
109
- }
110
- else if (p.type === 'tool-result') {
111
- yield { type: 'tool-result', output: p.output };
112
- }
113
- else if (p.type === 'error') {
114
- yield { type: 'error', error: String(p.error) };
115
- }
116
- }
117
- }
118
- catch (err) {
119
- const msg = `orchestrator import/stream failed: ${err?.message || String(err)}`;
120
- yield { type: 'error', error: msg };
121
- try {
122
- await this.core.addAssistantMessageToThread(threadId, msg, agent?.name || 'Assistant');
123
- }
124
- catch { }
125
- }
126
- }
127
- async sendUserMessage(threadId, message, agentConfig) {
128
- // 1. Add user message to thread (core)
129
- // const threadRepo = new ThreadRepository(this.repo);
130
- // await threadRepo.addThreadItem(
131
- // threadId,
132
- // message,
133
- // [], // contextDocumentIds
134
- // true, // addToThread
135
- // 'user-1', // userId
136
- // 'You', // userName
137
- // undefined, // userAvatarUrl
138
- // {type: 'message'},
139
- // );
140
- // 2. TODO: Start agent stream (mod-ai)
141
- // 3. TODO: As agent responses stream in, append to thread
142
- // 4. TODO: Notify UI of updates (if needed)
143
- }
144
- subscribeToThread(threadId, callback) {
145
- // TODO: Implement Automerge doc subscription for reactivity
146
- }
147
- }
@@ -1,96 +0,0 @@
1
- import { createStore } from 'zustand';
2
- import { useStore } from 'zustand';
3
- import { BranchableRepo } from '@mod/mod-core/services/branchable-repo';
4
- import { readModConfig } from '../services/mod-config.js';
5
- export const directoryStore = createStore((set, get) => ({
6
- items: {},
7
- rootIds: [],
8
- currentPath: [],
9
- expandedFolders: {},
10
- loading: false,
11
- error: null,
12
- setItems: (items, rootIds) => set(() => ({
13
- items: Object.fromEntries(items.map((i) => [i.id, i])),
14
- rootIds,
15
- })),
16
- setCurrentPath: (path) => set(() => ({ currentPath: path })),
17
- setExpanded: (folderId, expanded) => set((state) => ({
18
- expandedFolders: { ...state.expandedFolders, [folderId]: expanded }
19
- })),
20
- setLoading: (loading) => set(() => ({ loading })),
21
- setError: (error) => set(() => ({ error })),
22
- reset: () => set(() => ({
23
- items: {},
24
- rootIds: [],
25
- currentPath: [],
26
- expandedFolders: {},
27
- loading: false,
28
- error: null,
29
- })),
30
- async loadRootDirectory({ repo, selectedWorkspace, selectedThread }) {
31
- set({ loading: true, error: null });
32
- try {
33
- if (!repo) {
34
- set({ error: 'No repo available', loading: false });
35
- return;
36
- }
37
- const effectiveWorkspaceId = (selectedWorkspace && selectedWorkspace.id) || readModConfig()?.workspaceId;
38
- if (!effectiveWorkspaceId) {
39
- set({ error: 'No workspace selected or configured (.mod/config.json)', loading: false });
40
- return;
41
- }
42
- const branchContext = new BranchableRepo(repo);
43
- // Resolve branchId for consistent branch-aware view (prefer config > workspace.activeBranchId > selectedThread)
44
- let resolvedBranchId = undefined;
45
- try {
46
- resolvedBranchId = readModConfig()?.activeBranchId;
47
- if (!resolvedBranchId) {
48
- const wsHandle = await repo.find(effectiveWorkspaceId);
49
- const wsDoc = await wsHandle.doc();
50
- resolvedBranchId = wsDoc?.activeBranchId;
51
- }
52
- if (!resolvedBranchId && selectedThread?.branchId) {
53
- resolvedBranchId = selectedThread.branchId;
54
- }
55
- }
56
- catch { }
57
- const wsWrap = await branchContext.openHandle(effectiveWorkspaceId, { branchId: (resolvedBranchId || undefined), workspaceId: effectiveWorkspaceId });
58
- const wsDoc = wsWrap.doc();
59
- const fileRefs = wsDoc.fileRefs || [];
60
- const folders = wsDoc.folders || [];
61
- // Build root-level items and dedupe by name to avoid duplicates
62
- const itemMapByName = {};
63
- for (const f of folders) {
64
- if (!f.parentId) {
65
- const item = { id: f.id, name: f.name, type: 'folder', parentId: f.parentId || null };
66
- if (!itemMapByName[item.name])
67
- itemMapByName[item.name] = item;
68
- }
69
- }
70
- for (const f of fileRefs) {
71
- if (!f.folderId) {
72
- const item = { id: f.id, name: f.name, type: 'file', parentId: f.folderId || null };
73
- if (!itemMapByName[item.name])
74
- itemMapByName[item.name] = item;
75
- }
76
- }
77
- const items = Object.values(itemMapByName).sort((a, b) => a.name.localeCompare(b.name));
78
- const rootIds = items.map(i => i.id);
79
- get().setItems(items, rootIds);
80
- }
81
- catch (err) {
82
- set({ error: err.message || 'Failed to load directory' });
83
- }
84
- finally {
85
- set({ loading: false });
86
- }
87
- },
88
- }));
89
- // React hook for using the vanilla store in components
90
- export const useDirectoryStore = (selector) => useStore(directoryStore, selector);
91
- // Custom hook for loading directory (merged from use-directory)
92
- export function useDirectoryLoader() {
93
- const loadRootDirectory = useDirectoryStore((s) => s.loadRootDirectory);
94
- const reset = useDirectoryStore((s) => s.reset);
95
- return { loadRootDirectory, reset };
96
- }
@@ -1,46 +0,0 @@
1
- import { create } from 'zustand';
2
- import { BranchService } from '@mod/mod-core/services/branch-service';
3
- export const useThreadsStore = create((set) => ({
4
- threads: [],
5
- loading: false,
6
- error: null,
7
- fetchThreads: async (repo, selected) => {
8
- if (!selected || !repo)
9
- return;
10
- set({ loading: true, error: null });
11
- try {
12
- const wsHandle = await repo.find(selected.id);
13
- const wsDoc = wsHandle.doc();
14
- const threadList = [];
15
- if (wsDoc?.branchesDocId) {
16
- const branchService = new BranchService(repo);
17
- const branches = await branchService.getBranchesForWorkspace(wsDoc.branchesDocId);
18
- for (const branch of branches || []) {
19
- const branchId = String(branch?.id || '');
20
- if (!branchId)
21
- continue;
22
- const threadId = branch?.threadId ? String(branch.threadId) : branchId;
23
- let threadDoc = null;
24
- try {
25
- const tHandle = await repo.find(threadId);
26
- threadDoc = tHandle.doc();
27
- }
28
- catch { }
29
- threadList.push({
30
- id: String(threadDoc?.id || threadId),
31
- name: threadDoc?.name || branch?.name || 'Untitled',
32
- branchId,
33
- hasThread: Boolean(branch?.threadId),
34
- });
35
- }
36
- }
37
- set({ threads: threadList });
38
- }
39
- catch (err) {
40
- set({ error: err.message || String(err) });
41
- }
42
- finally {
43
- set({ loading: false });
44
- }
45
- },
46
- }));
@@ -1,54 +0,0 @@
1
- import { create } from 'zustand';
2
- import { readConfig } from '../lib/storage.js';
3
- export const useWorkspacesStore = create((set) => ({
4
- workspaces: [],
5
- loading: false,
6
- error: null,
7
- fetchWorkspaces: async (repo) => {
8
- set({ loading: true, error: null });
9
- try {
10
- const config = readConfig();
11
- const userDocId = config.auth?.userDocId;
12
- if (!userDocId) {
13
- set({ workspaces: [], error: 'Not authenticated. Run `mod auth login` first.' });
14
- return;
15
- }
16
- // Fetch workspaces from user document
17
- const userHandle = await repo.find(userDocId);
18
- await userHandle.whenReady();
19
- const userDoc = userHandle.doc();
20
- if (!userDoc) {
21
- set({ workspaces: [], error: 'Could not load user document.' });
22
- return;
23
- }
24
- const workspaceIds = userDoc.workspaceIds || [];
25
- const workspacesList = [];
26
- // Load workspace metadata for each workspace
27
- for (const wsId of workspaceIds) {
28
- try {
29
- const wsHandle = await repo.find(wsId);
30
- await wsHandle.whenReady();
31
- const ws = wsHandle.doc();
32
- workspacesList.push({
33
- id: wsId,
34
- name: ws?.title || ws?.name || 'Untitled',
35
- });
36
- }
37
- catch {
38
- // Workspace might not be available, add with just ID
39
- workspacesList.push({
40
- id: wsId,
41
- name: 'Untitled',
42
- });
43
- }
44
- }
45
- set({ workspaces: workspacesList });
46
- }
47
- catch (err) {
48
- set({ error: err.message || String(err) });
49
- }
50
- finally {
51
- set({ loading: false });
52
- }
53
- },
54
- }));
@@ -1,99 +0,0 @@
1
- // glassware[type="implementation", id="impl-cli-add-types--03cec234", requirements="requirement-cli-add-types--e6c546b5"]
2
- // spec: packages/mod-cli/specs/add.md
3
- /**
4
- * Create a typed AddError
5
- */
6
- export function createAddError(code, message, path, cause) {
7
- return { code, message, path, cause };
8
- }
9
- /**
10
- * Check if an error is an AddError
11
- */
12
- export function isAddError(error) {
13
- return (typeof error === 'object' &&
14
- error !== null &&
15
- 'code' in error &&
16
- 'message' in error);
17
- }
18
- /**
19
- * Error strategy mapping - defines how each error type should be handled
20
- */
21
- export const ERROR_STRATEGIES = {
22
- // Input validation - abort
23
- INVALID_PATH: { action: 'abort', userMessage: 'Show error, exit' },
24
- PATH_OUTSIDE_WORKSPACE: { action: 'abort', userMessage: 'Show error, exit' },
25
- NOT_CONNECTED: { action: 'abort', userMessage: 'Show error, suggest `mod init`' },
26
- // File validation - skip
27
- FILE_TOO_LARGE: { action: 'skip', userMessage: 'Warning, continue' },
28
- PERMISSION_DENIED: { action: 'skip', userMessage: 'Warning, continue' },
29
- ENCODING_ERROR: { action: 'fallback', userMessage: 'Auto-convert to binary, continue' },
30
- CIRCULAR_SYMLINK: { action: 'skip', userMessage: 'Warning, continue' },
31
- // Workspace errors - abort/skip
32
- WORKSPACE_NOT_FOUND: { action: 'abort', userMessage: 'Show error, exit' },
33
- FOLDER_NOT_FOUND: { action: 'skip', userMessage: 'Warning, continue' },
34
- FOLDER_LIMIT_EXCEEDED: { action: 'skip', userMessage: 'Warning, continue' },
35
- // Automerge errors - retry
36
- DOC_CREATE_FAILED: { action: 'retry', maxRetries: 3, userMessage: 'Retry, then skip' },
37
- DOC_UPDATE_FAILED: { action: 'retry', maxRetries: 3, userMessage: 'Retry, then skip' },
38
- SYNC_FAILED: { action: 'retry', maxRetries: 3, userMessage: 'Retry, then skip' },
39
- HANDLE_TIMEOUT: { action: 'retry', maxRetries: 3, userMessage: 'Retry, then skip' },
40
- // System errors - abort
41
- OUT_OF_MEMORY: { action: 'abort', userMessage: 'Show error, suggest smaller batch' },
42
- CANCELLED: { action: 'abort', userMessage: 'Show progress, exit' },
43
- UNKNOWN: { action: 'skip', userMessage: 'Log error, continue' },
44
- };
45
- /**
46
- * Get the error strategy for an error code
47
- */
48
- export function getErrorStrategy(code) {
49
- return ERROR_STRATEGIES[code];
50
- }
51
- /**
52
- * Constants for add operation
53
- */
54
- export const ADD_CONSTANTS = {
55
- /** Maximum concurrent file document creations */
56
- PARALLEL_FILE_LIMIT: 10,
57
- /** Maximum binary file size in bytes (100KB) */
58
- MAX_BINARY_SIZE: 100 * 1024,
59
- /** Progress update throttle in ms */
60
- PROGRESS_THROTTLE_MS: 100,
61
- /** Retry attempts for document operations */
62
- MAX_RETRIES: 3,
63
- /** Backoff multiplier for retries in ms */
64
- RETRY_BACKOFF_MS: 100,
65
- /** Memory limit in bytes (200MB) */
66
- MEMORY_LIMIT: 200 * 1024 * 1024,
67
- /** Small add threshold (no progress bar) */
68
- SMALL_ADD_THRESHOLD: 100,
69
- /** Medium add threshold (spinner) */
70
- MEDIUM_ADD_THRESHOLD: 1000,
71
- };
72
- /**
73
- * Known text file extensions
74
- */
75
- export const TEXT_EXTENSIONS = new Set([
76
- '.txt', '.md', '.js', '.ts', '.tsx', '.jsx', '.json', '.yaml', '.yml',
77
- '.toml', '.xml', '.html', '.css', '.scss', '.py', '.rb', '.go', '.rs',
78
- '.java', '.c', '.cpp', '.h', '.sh', '.sql', '.graphql', '.vue', '.svelte',
79
- '.astro', '.php', '.kt', '.scala', '.swift', '.dockerfile', '.tf', '.hcl',
80
- '.ps1', '.bat', '.cmd', '.env', '.gitignore', '.editorconfig', '.prettierrc',
81
- '.eslintrc', '.babelrc', 'makefile', 'dockerfile', '.lock'
82
- ]);
83
- /**
84
- * Default ignore patterns
85
- */
86
- export const DEFAULT_IGNORE_PATTERNS = [
87
- 'node_modules/',
88
- '.git/',
89
- 'dist/',
90
- 'build/',
91
- '.mod/',
92
- '*.log',
93
- '.DS_Store',
94
- 'Thumbs.db',
95
- '.env',
96
- '.env.local',
97
- '.env.*',
98
- 'secrets/',
99
- ];
@@ -1,16 +0,0 @@
1
- // glassware[type="implementation", id="cli-config-types--dd74d8f7", requirements="requirement-cli-storage-config-1--6e0f731a,requirement-cli-storage-config-2--f95aa032"]
2
- /**
3
- * Default settings values.
4
- */
5
- export const DEFAULT_SETTINGS = {
6
- syncDebounceMs: 100,
7
- logLevel: 'info',
8
- };
9
- /**
10
- * Default config for new installations.
11
- */
12
- export const DEFAULT_CONFIG = {
13
- version: 1,
14
- auth: null,
15
- settings: DEFAULT_SETTINGS,
16
- };
@@ -1,2 +0,0 @@
1
- export * from './config.js';
2
- export * from './workspace-connection.js';
@@ -1,53 +0,0 @@
1
- // glassware[type="implementation", id="cli-workspace-connection-types--13014cb2", requirements="requirement-cli-storage-conn-1--ce1b8ad9,requirement-cli-storage-conn-3--6536b6b2,requirement-cli-init-data-1--9f55757e"]
2
- // glassware[type="implementation", id="impl-cli-ws-type-connection--5c50b266", requirements="requirement-cli-ws-type-connection--bc33fa7a,requirement-cli-ws-conn-path--1a1a1301,requirement-cli-ws-conn-workspace-id--39acf87e,requirement-cli-ws-conn-name--791b4633,requirement-cli-ws-conn-connected-at--2ac4aba8,requirement-cli-ws-conn-last-synced--9b1ee720"]
3
- // spec: packages/mod-cli/specs/workspaces.md, packages/mod-cli/specs/initialization.md
4
- import path from 'path';
5
- // ISO 8601 regex pattern for timestamp validation
6
- const ISO8601_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
7
- // glassware[type="implementation", id="impl-cli-ws-conn-validate--c0c28675", requirements="requirement-cli-ws-conn-validate-path--1278682d,requirement-cli-ws-conn-validate-id--826810aa,requirement-cli-ws-conn-validate-timestamps--77971858"]
8
- /**
9
- * Validate a WorkspaceConnection object
10
- */
11
- export function validateWorkspaceConnection(conn) {
12
- const errors = [];
13
- if (!conn || typeof conn !== 'object') {
14
- return {
15
- valid: false,
16
- errors: [{ field: 'root', message: 'Connection must be an object', code: 'REQUIRED' }]
17
- };
18
- }
19
- const c = conn;
20
- // Validate path - must be absolute
21
- if (typeof c.path !== 'string' || !c.path) {
22
- errors.push({ field: 'path', message: 'path is required', code: 'REQUIRED' });
23
- }
24
- else if (!path.isAbsolute(c.path)) {
25
- errors.push({ field: 'path', message: 'path must be absolute', code: 'INVALID_PATH' });
26
- }
27
- // Validate workspaceId - must be non-empty string
28
- if (typeof c.workspaceId !== 'string' || !c.workspaceId) {
29
- errors.push({ field: 'workspaceId', message: 'workspaceId is required', code: 'REQUIRED' });
30
- }
31
- // Validate workspaceName - must be non-empty string
32
- if (typeof c.workspaceName !== 'string' || !c.workspaceName) {
33
- errors.push({ field: 'workspaceName', message: 'workspaceName is required', code: 'REQUIRED' });
34
- }
35
- // Validate connectedAt - must be ISO 8601
36
- if (typeof c.connectedAt !== 'string' || !c.connectedAt) {
37
- errors.push({ field: 'connectedAt', message: 'connectedAt is required', code: 'REQUIRED' });
38
- }
39
- else if (!ISO8601_REGEX.test(c.connectedAt)) {
40
- errors.push({ field: 'connectedAt', message: 'connectedAt must be ISO 8601 format', code: 'INVALID_FORMAT' });
41
- }
42
- // Validate lastSyncedAt - must be ISO 8601
43
- if (typeof c.lastSyncedAt !== 'string' || !c.lastSyncedAt) {
44
- errors.push({ field: 'lastSyncedAt', message: 'lastSyncedAt is required', code: 'REQUIRED' });
45
- }
46
- else if (!ISO8601_REGEX.test(c.lastSyncedAt)) {
47
- errors.push({ field: 'lastSyncedAt', message: 'lastSyncedAt must be ISO 8601 format', code: 'INVALID_FORMAT' });
48
- }
49
- return {
50
- valid: errors.length === 0,
51
- errors
52
- };
53
- }
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};