@empirical-gauzza/cli 1.0.37 → 1.0.39

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.
@@ -1,12 +1,15 @@
1
1
  'use strict';
2
2
 
3
- const { log, ask, uiSuccess } = require('./ui');
3
+ const { log, warn, ask, askYesNo, uiSuccess } = require('./ui');
4
4
  const { httpJson } = require('./http');
5
5
  const { getEnv, parseFlag, parseOption } = require('./env');
6
6
  const { ensureHeadlessToken, resolveAccessToken, buildApiAuthHeaders } = require('./auth');
7
7
  const { readStoredOAuthClient } = require('./oauthClient');
8
8
  const { deriveBaseUrlFromMcpUrl } = require('./constants');
9
9
  const { withCliAnalyticsContext } = require('./memoryCommands');
10
+ const { runWorkspaceInterview, sanitizeForTerminal } = require('./setupCommands');
11
+ const { isManualShapeAction, runManualShapeCommand } = require('./shapeCommands');
12
+ const { isInteractiveTerminal } = require('./empiricalTokensFormat');
10
13
  const {
11
14
  writeSelectedWorkspace,
12
15
  clearSelectedWorkspace,
@@ -18,9 +21,38 @@ const {
18
21
  WORKSPACE_ENV_VAR
19
22
  } = require('./workspaceContext');
20
23
 
24
+ const WORKSPACE_CREATE_INTERVIEW_USAGE = 'Usage: empirical workspace create --interview [--answers <file.json>] [--accept-cost]. The interview asks what the workspace is for, so it takes no name.';
25
+ const WORKSPACE_SHAPE_USAGE = 'Usage: empirical workspace shape [--workspace-id <id>] [--answers <file.json>] [--accept-cost]. Pass --workspace-id, or at a terminal bind this directory to the workspace first with `empirical workspace use <id>` and the command will ask you to confirm it.';
26
+ const WORKSPACE_SHAPE_EXPLICIT_ID = 'A re-shape is charged, so a script (--answers or --accept-cost) or a run with no terminal must name the workspace itself. Pass --workspace-id <id>; the workspace this directory is bound to is not used here.';
27
+
28
+ // Which workspace `workspace shape` re-shapes. An explicit --workspace-id is used as given. The ambient
29
+ // binding (env, project or global) is a convenience for a person at a terminal ONLY: it is shown by NAME
30
+ // and source and needs a yes that names the workspace, and it is never used by a script or with
31
+ // --accept-cost, so a stale binding cannot aim a paid re-shape at the wrong workspace unseen.
32
+ // Everything said here goes to stderr (warn), so a scripted run keeps stdout for its result.
33
+ async function resolveShapeTarget({ requested, local, answersFile, acceptCost, interactive, fetchName, askYesNo: confirm, say = warn }) {
34
+ if (requested) {
35
+ say(`Re-shaping workspace ${sanitizeForTerminal(requested)}.`);
36
+ return requested;
37
+ }
38
+ if (!local || !local.workspaceId) throw new Error(WORKSPACE_SHAPE_USAGE);
39
+ if (answersFile || acceptCost || !interactive) throw new Error(WORKSPACE_SHAPE_EXPLICIT_ID);
40
+ let name = '';
41
+ try { name = String((await fetchName(local.workspaceId)) || ''); } catch (_error) { name = ''; }
42
+ // The name can come from an AI-generated purpose, and everything below reaches the terminal: sanitised.
43
+ name = sanitizeForTerminal(name || String(local.label || ''));
44
+ const id = sanitizeForTerminal(local.workspaceId);
45
+ const source = sanitizeForTerminal(describeSelectionSource(local).trim().replace(/^\(|\)$/g, '')) || 'a local binding';
46
+ const shown = name ? `"${name}" (${id})` : id;
47
+ say(`No --workspace-id was given. This directory is bound to ${shown}: ${source}.`);
48
+ const yes = await confirm(`Re-shape ${name ? `"${name}"` : `workspace ${id}`}? This starts an interview that is charged.`, false, process.stderr);
49
+ if (!yes) throw new Error('Nothing was started. Pass --workspace-id <id> to name the workspace.');
50
+ return local.workspaceId;
51
+ }
52
+
21
53
  function isWorkspaceCommand(args) {
22
54
  if (args[0] !== 'workspace') return false;
23
- if (['list', 'current', 'select', 'use', 'unbind', 'create', 'delete'].includes(args[1])) return true;
55
+ if (['list', 'current', 'select', 'use', 'unbind', 'create', 'delete', 'shape'].includes(args[1])) return true;
24
56
  return args[1] === 'invite' && ['create', 'list', 'revoke'].includes(args[2]);
25
57
  }
26
58
 
@@ -40,7 +72,7 @@ function workspaceOptions(payload) {
40
72
  }
41
73
 
42
74
  function positionalArgs(args, startIndex) {
43
- const valueOptions = new Set(['--description', '--role', '--expires-in-days', '--max-uses']);
75
+ const valueOptions = new Set(['--description', '--role', '--expires-in-days', '--max-uses', '--answers', '--workspace-id']);
44
76
  const positional = [];
45
77
  for (let index = startIndex; index < args.length; index += 1) {
46
78
  if (valueOptions.has(args[index])) {
@@ -276,9 +308,48 @@ async function runWorkspaceCommand(args) {
276
308
  return;
277
309
  }
278
310
 
311
+ if (action === 'shape' && isManualShapeAction(args)) {
312
+ // Editing the shape by hand (show, set, edit, add-type, add-status, add-field): no interview, no charge.
313
+ // A dry run unless --apply, and a change is only applied to the shape that was previewed.
314
+ const bound = resolveLocalWorkspaceSelection();
315
+ return runManualShapeCommand({ args, baseUrl, authHeaders, resolveBound: () => (bound && bound.workspaceId) || '' });
316
+ }
317
+
318
+ if (action === 'shape') {
319
+ // Re-shapes a team workspace you manage through the interview. resolveShapeTarget decides which: an
320
+ // explicit --workspace-id, or (at a terminal only, after a named yes) the bound workspace.
321
+ const requested = parseOption(args, '--workspace-id', '').trim();
322
+ const answersFile = parseOption(args, '--answers', '');
323
+ const acceptCost = parseFlag(args, '--accept-cost');
324
+ const targetWorkspaceId = await resolveShapeTarget({
325
+ requested,
326
+ local: requested ? null : resolveLocalWorkspaceSelection(),
327
+ answersFile,
328
+ acceptCost,
329
+ interactive: isInteractiveTerminal(),
330
+ fetchName: async (id) => {
331
+ const payload = await httpJson(teamBrainsUrl, 'GET', null, authHeaders);
332
+ const hit = getTeamBrains(payload).find((b) => b.workspaceId === id);
333
+ return hit ? hit.name : '';
334
+ },
335
+ askYesNo
336
+ });
337
+ return runWorkspaceInterview({ targetWorkspaceId, answersFile, acceptCost });
338
+ }
339
+
340
+ if (action === 'create' && parseFlag(args, '--interview')) {
341
+ // Creating through the interview takes no name (it asks what the workspace is for), and it never
342
+ // re-shapes: with no --workspace-id it always makes a new workspace.
343
+ if (positionalArgs(args, 2).length || parseOption(args, '--workspace-id', '')) throw new Error(WORKSPACE_CREATE_INTERVIEW_USAGE);
344
+ return runWorkspaceInterview({
345
+ answersFile: parseOption(args, '--answers', ''),
346
+ acceptCost: parseFlag(args, '--accept-cost')
347
+ });
348
+ }
349
+
279
350
  if (action === 'create') {
280
351
  const name = positionalArgs(args, 2).join(' ').trim();
281
- if (!name) throw new Error('Usage: empirical workspace create <name> [--description <text>]');
352
+ if (!name) throw new Error('Usage: empirical workspace create <name> [--description <text>] or empirical workspace create --interview');
282
353
  const description = parseOption(args, '--description', '');
283
354
  const created = await httpJson(
284
355
  teamBrainsUrl,
@@ -394,5 +465,9 @@ module.exports = {
394
465
  isWorkspaceCommand,
395
466
  getWorkspaceCommandAuth,
396
467
  resolveWorkspaceTarget,
397
- runWorkspaceCommand
468
+ runWorkspaceCommand,
469
+ WORKSPACE_CREATE_INTERVIEW_USAGE,
470
+ WORKSPACE_SHAPE_USAGE,
471
+ WORKSPACE_SHAPE_EXPLICIT_ID,
472
+ resolveShapeTarget
398
473
  };
@@ -1,176 +1,176 @@
1
- 'use strict';
2
-
3
- const os = require('os');
4
- const path = require('path');
5
- const fs = require('fs');
6
- const { execFileSync } = require('child_process');
7
- const { httpJson } = require('./http');
8
- const { getEnv } = require('./env');
9
- const { resolveAccessToken, resolveApiBaseUrl, buildApiAuthHeaders } = require('./auth');
10
- const { readStoredOAuthClient } = require('./oauthClient');
11
- const { readJsonIfExists, writeFile, ensureDir } = require('./fsUtils');
12
-
13
- const WORKSPACE_CONTEXT_TIMEOUT_MS = 2000;
14
- const SELECTED_WORKSPACE_PATH = path.join(os.homedir(), '.empirical', 'workspace-selection.json');
15
-
16
- // Per-project binding (2026-09-24). The global selection above is one file per machine, so any
17
- // session in any repo flips it for every other session: a WOB session left the CLI on the WOB team
18
- // workspace and every tag-scoped recall from an empirical_v2 session returned [] while the memories
19
- // existed. The unit that actually varies is the project directory, like git config, so a project
20
- // can pin its workspace in <repo>/.empirical/workspace.json and `use` writes there by default when
21
- // run inside a git repo. Resolution order, first hit wins: env, project, global, then the server's
22
- // per-client selection exactly as before. No project file and no env means today's behavior.
23
- const PROJECT_WORKSPACE_DIRNAME = '.empirical';
24
- const PROJECT_WORKSPACE_FILENAME = 'workspace.json';
25
- const WORKSPACE_ENV_VAR = 'EMPIRICAL_WORKSPACE_ID';
26
-
27
- function readSelectedWorkspace() {
28
- const value = readJsonIfExists(SELECTED_WORKSPACE_PATH);
29
- return value && typeof value.workspaceId === 'string' ? value : null;
30
- }
31
-
32
- function writeSelectedWorkspace(workspace) {
33
- ensureDir(path.dirname(SELECTED_WORKSPACE_PATH));
34
- writeFile(SELECTED_WORKSPACE_PATH, `${JSON.stringify(workspace, null, 2)}\n`);
35
- }
36
-
37
- function clearSelectedWorkspace() {
38
- if (fs.existsSync(SELECTED_WORKSPACE_PATH)) fs.unlinkSync(SELECTED_WORKSPACE_PATH);
39
- }
40
-
41
- /**
42
- * Nearest <dir>/.empirical/workspace.json walking up from `startDir` to the filesystem root.
43
- * Returns { path, value } or null. A malformed file is ignored rather than fatal: a broken
44
- * binding must not take the CLI down, it just falls through to the next source.
45
- */
46
- function findProjectWorkspaceFile(startDir = process.cwd()) {
47
- let dir = path.resolve(startDir);
48
- for (;;) {
49
- const candidate = path.join(dir, PROJECT_WORKSPACE_DIRNAME, PROJECT_WORKSPACE_FILENAME);
50
- const value = readJsonIfExists(candidate);
51
- if (value && typeof value.workspaceId === 'string' && value.workspaceId.trim()) {
52
- return { path: candidate, value };
53
- }
54
- const parent = path.dirname(dir);
55
- if (parent === dir) return null;
56
- dir = parent;
57
- }
58
- }
59
-
60
- /**
61
- * The git toplevel for `cwd`, or null outside a repo (or without git). Used to decide where
62
- * `workspace use` writes by default and where the project binding lives.
63
- */
64
- function findGitRoot(cwd = process.cwd()) {
65
- try {
66
- const out = execFileSync('git', ['rev-parse', '--show-toplevel'], {
67
- cwd,
68
- stdio: ['ignore', 'pipe', 'ignore'],
69
- encoding: 'utf8'
70
- });
71
- const root = String(out || '').trim();
72
- return root ? path.resolve(root) : null;
73
- } catch (_error) {
74
- return null;
75
- }
76
- }
77
-
78
- function projectWorkspacePathFor(rootDir) {
79
- return path.join(rootDir, PROJECT_WORKSPACE_DIRNAME, PROJECT_WORKSPACE_FILENAME);
80
- }
81
-
82
- function writeProjectWorkspace(rootDir, workspace) {
83
- const filePath = projectWorkspacePathFor(rootDir);
84
- ensureDir(path.dirname(filePath));
85
- writeFile(filePath, `${JSON.stringify({ ...workspace, boundAt: new Date().toISOString() }, null, 2)}\n`);
86
- return filePath;
87
- }
88
-
89
- function clearProjectWorkspace(rootDir) {
90
- const filePath = projectWorkspacePathFor(rootDir);
91
- if (fs.existsSync(filePath)) {
92
- fs.unlinkSync(filePath);
93
- return filePath;
94
- }
95
- return null;
96
- }
97
-
98
- /**
99
- * The single answer to "which workspace does this CLI invocation act on", before asking the
100
- * server. Every memory command and `workspace current` read this, so reads and writes can never
101
- * disagree again. Returns { workspaceId, label, key, source, path } or null when nothing local
102
- * is set (the caller then falls back to the server's per-client selection).
103
- */
104
- function resolveLocalWorkspaceSelection({ cwd = process.cwd(), env = process.env } = {}) {
105
- const fromEnv = String(env[WORKSPACE_ENV_VAR] || '').trim();
106
- if (fromEnv) {
107
- return { workspaceId: fromEnv, label: null, key: null, source: 'env', path: WORKSPACE_ENV_VAR };
108
- }
109
- const project = findProjectWorkspaceFile(cwd);
110
- if (project) {
111
- const { workspaceId, label = null, key = null } = project.value;
112
- return { workspaceId: workspaceId.trim(), label, key, source: 'project', path: project.path };
113
- }
114
- const global = readSelectedWorkspace();
115
- if (global) {
116
- return { workspaceId: global.workspaceId, label: global.label || null, key: global.key || null, source: 'global', path: SELECTED_WORKSPACE_PATH };
117
- }
118
- return null;
119
- }
120
-
121
- async function withSelectedWorkspaceContext(fn) {
122
- const local = resolveLocalWorkspaceSelection();
123
- if (local?.workspaceId) {
124
- const previousWorkspaceId = globalThis.__empiricalCliWorkspaceId || null;
125
- globalThis.__empiricalCliWorkspaceId = local.workspaceId;
126
- try {
127
- return await fn();
128
- } finally {
129
- globalThis.__empiricalCliWorkspaceId = previousWorkspaceId;
130
- }
131
- }
132
-
133
- const accessToken = resolveAccessToken();
134
- const clientId = getEnv('EMPIRICAL_CLIENT_ID') || readStoredOAuthClient()?.clientId;
135
- if (!accessToken || !clientId) return fn();
136
-
137
- const previousWorkspaceId = globalThis.__empiricalCliWorkspaceId || null;
138
- globalThis.__empiricalCliWorkspaceId = null;
139
- try {
140
- const baseUrl = resolveApiBaseUrl();
141
- const currentUrl = new URL(
142
- `/oauth/clients/${encodeURIComponent(clientId)}/workspace-selection`,
143
- baseUrl
144
- ).toString();
145
- try {
146
- const payload = await httpJson(
147
- currentUrl,
148
- 'GET',
149
- null,
150
- buildApiAuthHeaders(accessToken),
151
- WORKSPACE_CONTEXT_TIMEOUT_MS
152
- );
153
- globalThis.__empiricalCliWorkspaceId = payload?.current?.workspaceId || null;
154
- } catch (_error) {
155
- globalThis.__empiricalCliWorkspaceId = null;
156
- }
157
- return await fn();
158
- } finally {
159
- globalThis.__empiricalCliWorkspaceId = previousWorkspaceId;
160
- }
161
- }
162
-
163
- module.exports = {
164
- withSelectedWorkspaceContext,
165
- resolveLocalWorkspaceSelection,
166
- readSelectedWorkspace,
167
- writeSelectedWorkspace,
168
- clearSelectedWorkspace,
169
- findProjectWorkspaceFile,
170
- findGitRoot,
171
- projectWorkspacePathFor,
172
- writeProjectWorkspace,
173
- clearProjectWorkspace,
174
- WORKSPACE_ENV_VAR,
175
- SELECTED_WORKSPACE_PATH
176
- };
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const { execFileSync } = require('child_process');
7
+ const { httpJson } = require('./http');
8
+ const { getEnv } = require('./env');
9
+ const { resolveAccessToken, resolveApiBaseUrl, buildApiAuthHeaders } = require('./auth');
10
+ const { readStoredOAuthClient } = require('./oauthClient');
11
+ const { readJsonIfExists, writeFile, ensureDir } = require('./fsUtils');
12
+
13
+ const WORKSPACE_CONTEXT_TIMEOUT_MS = 2000;
14
+ const SELECTED_WORKSPACE_PATH = path.join(os.homedir(), '.empirical', 'workspace-selection.json');
15
+
16
+ // Per-project binding (2026-09-24). The global selection above is one file per machine, so any
17
+ // session in any repo flips it for every other session: a WOB session left the CLI on the WOB team
18
+ // workspace and every tag-scoped recall from an empirical_v2 session returned [] while the memories
19
+ // existed. The unit that actually varies is the project directory, like git config, so a project
20
+ // can pin its workspace in <repo>/.empirical/workspace.json and `use` writes there by default when
21
+ // run inside a git repo. Resolution order, first hit wins: env, project, global, then the server's
22
+ // per-client selection exactly as before. No project file and no env means today's behavior.
23
+ const PROJECT_WORKSPACE_DIRNAME = '.empirical';
24
+ const PROJECT_WORKSPACE_FILENAME = 'workspace.json';
25
+ const WORKSPACE_ENV_VAR = 'EMPIRICAL_WORKSPACE_ID';
26
+
27
+ function readSelectedWorkspace() {
28
+ const value = readJsonIfExists(SELECTED_WORKSPACE_PATH);
29
+ return value && typeof value.workspaceId === 'string' ? value : null;
30
+ }
31
+
32
+ function writeSelectedWorkspace(workspace) {
33
+ ensureDir(path.dirname(SELECTED_WORKSPACE_PATH));
34
+ writeFile(SELECTED_WORKSPACE_PATH, `${JSON.stringify(workspace, null, 2)}\n`);
35
+ }
36
+
37
+ function clearSelectedWorkspace() {
38
+ if (fs.existsSync(SELECTED_WORKSPACE_PATH)) fs.unlinkSync(SELECTED_WORKSPACE_PATH);
39
+ }
40
+
41
+ /**
42
+ * Nearest <dir>/.empirical/workspace.json walking up from `startDir` to the filesystem root.
43
+ * Returns { path, value } or null. A malformed file is ignored rather than fatal: a broken
44
+ * binding must not take the CLI down, it just falls through to the next source.
45
+ */
46
+ function findProjectWorkspaceFile(startDir = process.cwd()) {
47
+ let dir = path.resolve(startDir);
48
+ for (;;) {
49
+ const candidate = path.join(dir, PROJECT_WORKSPACE_DIRNAME, PROJECT_WORKSPACE_FILENAME);
50
+ const value = readJsonIfExists(candidate);
51
+ if (value && typeof value.workspaceId === 'string' && value.workspaceId.trim()) {
52
+ return { path: candidate, value };
53
+ }
54
+ const parent = path.dirname(dir);
55
+ if (parent === dir) return null;
56
+ dir = parent;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * The git toplevel for `cwd`, or null outside a repo (or without git). Used to decide where
62
+ * `workspace use` writes by default and where the project binding lives.
63
+ */
64
+ function findGitRoot(cwd = process.cwd()) {
65
+ try {
66
+ const out = execFileSync('git', ['rev-parse', '--show-toplevel'], {
67
+ cwd,
68
+ stdio: ['ignore', 'pipe', 'ignore'],
69
+ encoding: 'utf8'
70
+ });
71
+ const root = String(out || '').trim();
72
+ return root ? path.resolve(root) : null;
73
+ } catch (_error) {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ function projectWorkspacePathFor(rootDir) {
79
+ return path.join(rootDir, PROJECT_WORKSPACE_DIRNAME, PROJECT_WORKSPACE_FILENAME);
80
+ }
81
+
82
+ function writeProjectWorkspace(rootDir, workspace) {
83
+ const filePath = projectWorkspacePathFor(rootDir);
84
+ ensureDir(path.dirname(filePath));
85
+ writeFile(filePath, `${JSON.stringify({ ...workspace, boundAt: new Date().toISOString() }, null, 2)}\n`);
86
+ return filePath;
87
+ }
88
+
89
+ function clearProjectWorkspace(rootDir) {
90
+ const filePath = projectWorkspacePathFor(rootDir);
91
+ if (fs.existsSync(filePath)) {
92
+ fs.unlinkSync(filePath);
93
+ return filePath;
94
+ }
95
+ return null;
96
+ }
97
+
98
+ /**
99
+ * The single answer to "which workspace does this CLI invocation act on", before asking the
100
+ * server. Every memory command and `workspace current` read this, so reads and writes can never
101
+ * disagree again. Returns { workspaceId, label, key, source, path } or null when nothing local
102
+ * is set (the caller then falls back to the server's per-client selection).
103
+ */
104
+ function resolveLocalWorkspaceSelection({ cwd = process.cwd(), env = process.env } = {}) {
105
+ const fromEnv = String(env[WORKSPACE_ENV_VAR] || '').trim();
106
+ if (fromEnv) {
107
+ return { workspaceId: fromEnv, label: null, key: null, source: 'env', path: WORKSPACE_ENV_VAR };
108
+ }
109
+ const project = findProjectWorkspaceFile(cwd);
110
+ if (project) {
111
+ const { workspaceId, label = null, key = null } = project.value;
112
+ return { workspaceId: workspaceId.trim(), label, key, source: 'project', path: project.path };
113
+ }
114
+ const global = readSelectedWorkspace();
115
+ if (global) {
116
+ return { workspaceId: global.workspaceId, label: global.label || null, key: global.key || null, source: 'global', path: SELECTED_WORKSPACE_PATH };
117
+ }
118
+ return null;
119
+ }
120
+
121
+ async function withSelectedWorkspaceContext(fn) {
122
+ const local = resolveLocalWorkspaceSelection();
123
+ if (local?.workspaceId) {
124
+ const previousWorkspaceId = globalThis.__empiricalCliWorkspaceId || null;
125
+ globalThis.__empiricalCliWorkspaceId = local.workspaceId;
126
+ try {
127
+ return await fn();
128
+ } finally {
129
+ globalThis.__empiricalCliWorkspaceId = previousWorkspaceId;
130
+ }
131
+ }
132
+
133
+ const accessToken = resolveAccessToken();
134
+ const clientId = getEnv('EMPIRICAL_CLIENT_ID') || readStoredOAuthClient()?.clientId;
135
+ if (!accessToken || !clientId) return fn();
136
+
137
+ const previousWorkspaceId = globalThis.__empiricalCliWorkspaceId || null;
138
+ globalThis.__empiricalCliWorkspaceId = null;
139
+ try {
140
+ const baseUrl = resolveApiBaseUrl();
141
+ const currentUrl = new URL(
142
+ `/oauth/clients/${encodeURIComponent(clientId)}/workspace-selection`,
143
+ baseUrl
144
+ ).toString();
145
+ try {
146
+ const payload = await httpJson(
147
+ currentUrl,
148
+ 'GET',
149
+ null,
150
+ buildApiAuthHeaders(accessToken),
151
+ WORKSPACE_CONTEXT_TIMEOUT_MS
152
+ );
153
+ globalThis.__empiricalCliWorkspaceId = payload?.current?.workspaceId || null;
154
+ } catch (_error) {
155
+ globalThis.__empiricalCliWorkspaceId = null;
156
+ }
157
+ return await fn();
158
+ } finally {
159
+ globalThis.__empiricalCliWorkspaceId = previousWorkspaceId;
160
+ }
161
+ }
162
+
163
+ module.exports = {
164
+ withSelectedWorkspaceContext,
165
+ resolveLocalWorkspaceSelection,
166
+ readSelectedWorkspace,
167
+ writeSelectedWorkspace,
168
+ clearSelectedWorkspace,
169
+ findProjectWorkspaceFile,
170
+ findGitRoot,
171
+ projectWorkspacePathFor,
172
+ writeProjectWorkspace,
173
+ clearProjectWorkspace,
174
+ WORKSPACE_ENV_VAR,
175
+ SELECTED_WORKSPACE_PATH
176
+ };
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
- {
2
- "name": "@empirical-gauzza/cli",
3
- "version": "1.0.37",
4
- "description": "Persistent cross-tool memory for AI agents. Give Claude Code, Codex, Copilot, Cursor, and ChatGPT one shared memory: save project context once, recall it everywhere via CLI or MCP.",
5
- "keywords": [
6
- "memory",
7
- "mcp",
8
- "agent memory",
9
- "ai memory",
10
- "claude",
11
- "chatgpt",
12
- "model context protocol",
13
- "persistent memory",
14
- "claude-code",
15
- "codex",
16
- "knowledge base"
17
- ],
18
- "type": "commonjs",
19
- "license": "MIT",
20
- "homepage": "https://empirical.gauzza.com/docs/cli-details",
21
- "publishConfig": {
22
- "access": "public"
23
- },
24
- "bin": {
25
- "empirical": "bin/empirical.js"
26
- },
27
- "engines": {
28
- "node": ">=18.0.0"
29
- },
30
- "dependencies": {
31
- "gray-matter": "^4.0.3"
32
- }
33
- }
1
+ {
2
+ "name": "@empirical-gauzza/cli",
3
+ "version": "1.0.39",
4
+ "description": "Persistent cross-tool memory for AI agents. Give Claude Code, Codex, Copilot, Cursor, and ChatGPT one shared memory: save project context once, recall it everywhere via CLI or MCP.",
5
+ "keywords": [
6
+ "memory",
7
+ "mcp",
8
+ "agent memory",
9
+ "ai memory",
10
+ "claude",
11
+ "chatgpt",
12
+ "model context protocol",
13
+ "persistent memory",
14
+ "claude-code",
15
+ "codex",
16
+ "knowledge base"
17
+ ],
18
+ "type": "commonjs",
19
+ "license": "MIT",
20
+ "homepage": "https://empirical.gauzza.com/docs/cli-details",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "bin": {
25
+ "empirical": "bin/empirical.js"
26
+ },
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ },
30
+ "dependencies": {
31
+ "gray-matter": "^4.0.3"
32
+ }
33
+ }