fuli-context 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +178 -0
- package/compose.graphiti.yml +86 -0
- package/dist/web/assets/ConnectionsPage-CY6H_mJJ.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BSpz5Sfm.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CHbB_0rC.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BlEvhPrv.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-CvjGPlUc.js +1 -0
- package/dist/web/assets/KnowledgePage-Khuo3cNs.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-DVZusvem.js +4 -0
- package/dist/web/assets/OverviewPage-DC4C4Fhn.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CSV9Mtgz.js +1 -0
- package/dist/web/assets/PersonalProfilePage-CVJ3TcRi.css +1 -0
- package/dist/web/assets/PersonalProjectsPage-A50EaseQ.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BAdO_bae.js +1 -0
- package/dist/web/assets/ReviewPage-Dztk2jDG.js +1 -0
- package/dist/web/assets/console-tbvTUi6p.js +1 -0
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-B3s5CXhv.js +1 -0
- package/dist/web/assets/index-Devf7glZ.css +1 -0
- package/dist/web/assets/index-DwUDhQKc.js +6 -0
- package/dist/web/assets/model-DVT8sjy9.js +1 -0
- package/dist/web/assets/paths-B790iniO.js +1 -0
- package/dist/web/assets/runtime-dom.esm-bundler-DttsK21F.js +1 -0
- package/dist/web/assets/vue-router-BYTAoWT3.js +1 -0
- package/dist/web/index.html +22 -0
- package/graph-provider/Dockerfile +16 -0
- package/graph-provider/fuli_graph/__init__.py +5 -0
- package/graph-provider/fuli_graph/app.py +547 -0
- package/graph-provider/fuli_graph/auth.py +17 -0
- package/graph-provider/fuli_graph/collaboration_context.py +290 -0
- package/graph-provider/fuli_graph/config.py +34 -0
- package/graph-provider/fuli_graph/graph_models.py +123 -0
- package/graph-provider/fuli_graph/graph_projection.py +405 -0
- package/graph-provider/fuli_graph/graph_query.py +561 -0
- package/graph-provider/fuli_graph/knowledge_audit.py +765 -0
- package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +246 -0
- package/graph-provider/fuli_graph/knowledge_management.py +816 -0
- package/graph-provider/fuli_graph/knowledge_records.py +65 -0
- package/graph-provider/fuli_graph/knowledge_search.py +723 -0
- package/graph-provider/fuli_graph/knowledge_usage_models.py +52 -0
- package/graph-provider/fuli_graph/model_validation.py +58 -0
- package/graph-provider/fuli_graph/models.py +1179 -0
- package/graph-provider/fuli_graph/personal_project_access.py +29 -0
- package/graph-provider/fuli_graph/preference_conflicts.py +420 -0
- package/graph-provider/fuli_graph/project_action_models.py +142 -0
- package/graph-provider/fuli_graph/project_knowledge.py +707 -0
- package/graph-provider/fuli_graph/project_management.py +251 -0
- package/graph-provider/fuli_graph/provider_values.py +40 -0
- package/graph-provider/fuli_graph/runtime.py +350 -0
- package/graph-provider/fuli_graph/search_projection.py +156 -0
- package/graph-provider/fuli_graph/store.py +24 -0
- package/graph-provider/fuli_graph/store_identity.py +237 -0
- package/graph-provider/fuli_graph/store_knowledge.py +447 -0
- package/graph-provider/fuli_graph/store_projects.py +242 -0
- package/graph-provider/fuli_graph/store_publication.py +370 -0
- package/graph-provider/fuli_graph/store_records.py +134 -0
- package/graph-provider/pyproject.toml +35 -0
- package/npm-shrinkwrap.json +6045 -0
- package/package.json +99 -0
- package/skills/capturing-session-knowledge/SKILL.md +226 -0
- package/skills/capturing-session-knowledge/agents/openai.yaml +13 -0
- package/skills/grilling-project/SKILL.md +129 -0
- package/skills/grilling-project/agents/openai.yaml +4 -0
- package/skills/grilling-project/references/project-profile.md +105 -0
- package/src/agent/candidate-read-service.js +53 -0
- package/src/agent/context-pack-service.js +83 -0
- package/src/agent/public-read-policy.js +39 -0
- package/src/agent/public-read-service.js +69 -0
- package/src/agent/read-projection.js +107 -0
- package/src/agent/search-service.js +61 -0
- package/src/agent/space-boundary.js +33 -0
- package/src/agent-tools/graph-definitions.js +479 -0
- package/src/agent-tools/graph-handlers.js +96 -0
- package/src/agent-tools/schema.js +37 -0
- package/src/agent-tools.js +16 -0
- package/src/app/application-error.js +13 -0
- package/src/app/bootstrap.js +8 -0
- package/src/app/create-application.js +143 -0
- package/src/app/state-service.js +38 -0
- package/src/candidates.js +78 -0
- package/src/classifier.js +63 -0
- package/src/cli/command-arguments.js +14 -0
- package/src/cli/command-registry.js +78 -0
- package/src/cli/commands/candidates.js +17 -0
- package/src/cli/commands/ingestion.js +32 -0
- package/src/cli/commands/reads.js +50 -0
- package/src/cli/commands/spaces.js +20 -0
- package/src/cli/invocation.js +47 -0
- package/src/cli/local-runtime-command.js +94 -0
- package/src/cli/local-runtime-options.js +64 -0
- package/src/cli/migrate-command.js +71 -0
- package/src/cli/migration-destination.js +117 -0
- package/src/cli/setup-agent-selection.js +195 -0
- package/src/cli/setup-command.js +85 -0
- package/src/cli/uninstall-command.js +67 -0
- package/src/cli.js +90 -0
- package/src/context-pack.js +206 -0
- package/src/defaults.js +1 -0
- package/src/extractor.js +77 -0
- package/src/fact-history.js +39 -0
- package/src/facts/fact-service.js +89 -0
- package/src/graphiti/agent-access-policy.js +88 -0
- package/src/graphiti/capture-policy.js +84 -0
- package/src/graphiti/federated-application.js +1292 -0
- package/src/graphiti/personal-context.js +29 -0
- package/src/graphiti/project-path-context.js +159 -0
- package/src/graphiti/provider-client.js +288 -0
- package/src/graphiti/runtime-config.js +85 -0
- package/src/graphiti/setup.js +43 -0
- package/src/graphiti/source-marker.js +142 -0
- package/src/http/api-router.js +133 -0
- package/src/http/bounded-json-array.js +19 -0
- package/src/http/error-mapping.js +36 -0
- package/src/http/graph-api-router.js +257 -0
- package/src/http/lens-route.js +50 -0
- package/src/http/request-policy.js +41 -0
- package/src/http/response.js +29 -0
- package/src/http/static-handler.js +55 -0
- package/src/ingestion.js +161 -0
- package/src/lens/lens-correction-service.js +81 -0
- package/src/lens/lens-fact-writer.js +85 -0
- package/src/lens/lens-input.js +62 -0
- package/src/lens/lens-query-projection.js +87 -0
- package/src/lens/lens-query-ranking.js +62 -0
- package/src/lens/lens-query.js +143 -0
- package/src/lens/lens-resource-projection.js +93 -0
- package/src/lens/lens-resource-selection.js +45 -0
- package/src/lens/lens-resource-service.js +103 -0
- package/src/lens/lens-search-query.js +122 -0
- package/src/lens/lens-service.js +183 -0
- package/src/local-runtime/lifecycle.js +254 -0
- package/src/mcp/create-mcp-server.js +85 -0
- package/src/mcp/instructions.js +10 -0
- package/src/mcp/project-action-preview-tokens.js +72 -0
- package/src/mcp/runtime.js +71 -0
- package/src/mcp/tool-annotations.js +63 -0
- package/src/mcp/tool-result.js +167 -0
- package/src/mcp/tool-schema.js +60 -0
- package/src/mcp-server.js +76 -0
- package/src/models.js +44 -0
- package/src/observer.js +83 -0
- package/src/package-metadata.js +6 -0
- package/src/project-rules.js +33 -0
- package/src/publication/canonical-json.js +39 -0
- package/src/publication/outbox-service.js +46 -0
- package/src/publication/publication-envelope-schema.js +75 -0
- package/src/publication/publication-envelope.js +37 -0
- package/src/publication/publication-policy.js +82 -0
- package/src/publication/publication-service.js +48 -0
- package/src/router.js +58 -0
- package/src/runtime-options.js +139 -0
- package/src/security/episode-visibility.js +14 -0
- package/src/security/fact-visibility.js +15 -0
- package/src/security/sensitive-content.js +18 -0
- package/src/security/source-metadata-policy.js +8 -0
- package/src/server/application-lifecycle.js +34 -0
- package/src/server/listen.js +24 -0
- package/src/server.js +135 -0
- package/src/setup/agent-installation-status.js +97 -0
- package/src/setup/agents.js +123 -0
- package/src/setup/codex-bootstrap.js +118 -0
- package/src/setup/codex-config.js +122 -0
- package/src/setup/config-backup.js +22 -0
- package/src/setup/container-runtime.js +388 -0
- package/src/setup/cursor-config.js +61 -0
- package/src/setup/graph-runtime.js +360 -0
- package/src/setup/node-runtime.js +24 -0
- package/src/setup/options.js +91 -0
- package/src/setup/paths.js +47 -0
- package/src/setup/session-skill.js +91 -0
- package/src/setup/setup.js +85 -0
- package/src/setup/uninstall.js +90 -0
- package/src/storage/episode-evidence-preview.js +100 -0
- package/src/storage/fact-search-order.js +18 -0
- package/src/storage/file/episode-evidence.js +37 -0
- package/src/storage/file/episode-index.js +59 -0
- package/src/storage/file/fact-search.js +60 -0
- package/src/storage/file-store.js +437 -0
- package/src/storage/import-json.js +70 -0
- package/src/storage/json-file.js +41 -0
- package/src/storage/legacy-record-validation.js +152 -0
- package/src/storage/legacy-snapshot-validation.js +32 -0
- package/src/storage/migrate.js +128 -0
- package/src/storage/migrations/001-initial.sql +83 -0
- package/src/storage/migrations/002-storage-integrity.sql +113 -0
- package/src/storage/record-validation.js +46 -0
- package/src/storage/snapshot-defaults.js +54 -0
- package/src/storage/sqlite/candidates-repository.js +84 -0
- package/src/storage/sqlite/episode-evidence-repository.js +154 -0
- package/src/storage/sqlite/episode-privacy-sql.js +33 -0
- package/src/storage/sqlite/episodes-repository.js +52 -0
- package/src/storage/sqlite/fact-search-repository.js +162 -0
- package/src/storage/sqlite/facts-repository.js +146 -0
- package/src/storage/sqlite/imports-repository.js +39 -0
- package/src/storage/sqlite/mapper.js +84 -0
- package/src/storage/sqlite/open-database.js +15 -0
- package/src/storage/sqlite/outbox-repository.js +103 -0
- package/src/storage/sqlite/snapshot-repository.js +69 -0
- package/src/storage/sqlite/spaces-repository.js +52 -0
- package/src/storage/sqlite/subscriptions-repository.js +54 -0
- package/src/storage/sqlite-store.js +159 -0
- package/src/storage/stable-json.js +24 -0
- package/src/storage/store-port.js +20 -0
- package/src/store.js +2 -0
- package/src/workspace-protocol/array-validation.js +19 -0
- package/src/workspace-protocol/base64url-contract.js +27 -0
- package/src/workspace-protocol/constants.js +37 -0
- package/src/workspace-protocol/error-contract.js +24 -0
- package/src/workspace-protocol/fact-contract.js +110 -0
- package/src/workspace-protocol/governance-contract.js +47 -0
- package/src/workspace-protocol/index.js +53 -0
- package/src/workspace-protocol/json-budget.js +19 -0
- package/src/workspace-protocol/manifest-contract.js +186 -0
- package/src/workspace-protocol/publication-contract.js +212 -0
- package/src/workspace-protocol/query-contract.js +149 -0
- package/src/workspace-protocol/scalar-contract.js +192 -0
- package/src/workspace-protocol/sync-contract.js +184 -0
- package/src/workspace-protocol/text-contract.js +39 -0
- package/src/workspace-protocol/validation.js +66 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { DEFAULT_FULI_PORT } from '../defaults.js';
|
|
2
|
+
|
|
3
|
+
const VALUE_OPTIONS = Object.freeze({
|
|
4
|
+
'--data-dir': 'dataDir',
|
|
5
|
+
'--personal-space': 'personalSpaceName',
|
|
6
|
+
'--port': 'port'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const BOOLEAN_OPTIONS = Object.freeze({
|
|
10
|
+
'--open': 'open',
|
|
11
|
+
'--rebuild': 'rebuild',
|
|
12
|
+
'--json': 'json'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const ALLOWED_BY_COMMAND = Object.freeze({
|
|
16
|
+
start: new Set(['--data-dir', '--personal-space', '--port', '--open', '--rebuild']),
|
|
17
|
+
stop: new Set(['--data-dir']),
|
|
18
|
+
restart: new Set(['--data-dir', '--personal-space', '--port', '--open', '--rebuild']),
|
|
19
|
+
status: new Set(['--data-dir', '--port', '--json']),
|
|
20
|
+
open: new Set(['--data-dir'])
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export function parseLocalRuntimeOptions(command, args = []) {
|
|
24
|
+
const allowed = ALLOWED_BY_COMMAND[command];
|
|
25
|
+
if (!allowed) throw new TypeError(`Unknown local runtime command: ${command}`);
|
|
26
|
+
const result = {
|
|
27
|
+
dataDir: null,
|
|
28
|
+
personalSpaceName: '我',
|
|
29
|
+
port: DEFAULT_FULI_PORT,
|
|
30
|
+
open: false,
|
|
31
|
+
rebuild: false,
|
|
32
|
+
json: false
|
|
33
|
+
};
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
|
|
36
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
37
|
+
const flag = args[index];
|
|
38
|
+
if (!allowed.has(flag)) throw new TypeError(`Unknown ${command} option: ${flag}`);
|
|
39
|
+
if (seen.has(flag)) throw new TypeError(`Duplicate ${flag}`);
|
|
40
|
+
seen.add(flag);
|
|
41
|
+
|
|
42
|
+
const booleanKey = BOOLEAN_OPTIONS[flag];
|
|
43
|
+
if (booleanKey) {
|
|
44
|
+
result[booleanKey] = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const valueKey = VALUE_OPTIONS[flag];
|
|
48
|
+
const value = args[index + 1];
|
|
49
|
+
if (!valueKey || typeof value !== 'string' || !value.trim() || value.startsWith('--')) {
|
|
50
|
+
throw new TypeError(`Missing value for ${flag}`);
|
|
51
|
+
}
|
|
52
|
+
result[valueKey] = valueKey === 'port' ? parsePort(value) : value.trim();
|
|
53
|
+
index += 1;
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parsePort(value) {
|
|
59
|
+
const port = Number(value);
|
|
60
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
61
|
+
throw new TypeError('--port must be between 1 and 65535');
|
|
62
|
+
}
|
|
63
|
+
return port;
|
|
64
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
applyPreparedJsonSnapshot,
|
|
5
|
+
prepareJsonSnapshot
|
|
6
|
+
} from '../storage/import-json.js';
|
|
7
|
+
import { SqliteStore } from '../storage/sqlite-store.js';
|
|
8
|
+
import {
|
|
9
|
+
cleanupMigrationDestination,
|
|
10
|
+
planMigrationDestination,
|
|
11
|
+
publishMigrationDestination
|
|
12
|
+
} from './migration-destination.js';
|
|
13
|
+
|
|
14
|
+
export function migrateLegacyJson(args, { createStore = (path) => new SqliteStore(path) } = {}) {
|
|
15
|
+
const sourcePath = option(args, '--from');
|
|
16
|
+
const destinationPath = option(args, '--to');
|
|
17
|
+
if (!sourcePath || !destinationPath) throw new Error('migrate requires --from and --to');
|
|
18
|
+
|
|
19
|
+
const snapshot = readSnapshot(sourcePath);
|
|
20
|
+
const prepared = prepareJsonSnapshot(snapshot, sourcePath);
|
|
21
|
+
const destination = planMigrationDestination(destinationPath);
|
|
22
|
+
const replace = args.includes('--replace');
|
|
23
|
+
let store;
|
|
24
|
+
let result;
|
|
25
|
+
let failure;
|
|
26
|
+
try {
|
|
27
|
+
store = createStore(destination.storePath);
|
|
28
|
+
result = applyPreparedJsonSnapshot(store, prepared, { replace });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
failure = error;
|
|
31
|
+
} finally {
|
|
32
|
+
try {
|
|
33
|
+
store?.close();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
failure ??= error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!failure) {
|
|
39
|
+
try {
|
|
40
|
+
publishMigrationDestination(destination);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
failure = error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
cleanupMigrationDestination(destination);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
failure ??= error;
|
|
49
|
+
}
|
|
50
|
+
if (failure) throw failure;
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readSnapshot(sourcePath) {
|
|
55
|
+
let source;
|
|
56
|
+
try {
|
|
57
|
+
source = readFileSync(sourcePath, 'utf8');
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(`Unable to read ${sourcePath}: ${error.message}`);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(source);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error(`Invalid JSON in ${sourcePath}: ${error.message}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function option(args, flag) {
|
|
69
|
+
const index = args.indexOf(flag);
|
|
70
|
+
return index === -1 ? null : args[index + 1] ?? null;
|
|
71
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
linkSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
rmdirSync,
|
|
10
|
+
unlinkSync
|
|
11
|
+
} from 'node:fs';
|
|
12
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
13
|
+
|
|
14
|
+
export function planMigrationDestination(destinationPath) {
|
|
15
|
+
const destination = resolve(destinationPath);
|
|
16
|
+
if (existsSync(destination)) {
|
|
17
|
+
return {
|
|
18
|
+
destination,
|
|
19
|
+
storePath: destination,
|
|
20
|
+
ownsStorePath: false,
|
|
21
|
+
createdParents: []
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const parent = dirname(destination);
|
|
26
|
+
const createdParents = createParents(parent);
|
|
27
|
+
try {
|
|
28
|
+
return {
|
|
29
|
+
destination,
|
|
30
|
+
storePath: reserveTemporaryPath(destination),
|
|
31
|
+
ownsStorePath: true,
|
|
32
|
+
createdParents
|
|
33
|
+
};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
removeEmptyParents(createdParents);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function publishMigrationDestination(plan) {
|
|
41
|
+
if (!plan.ownsStorePath) return;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
linkSync(plan.storePath, plan.destination);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (existsSync(plan.destination)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Migration destination appeared while the import was running: ${plan.destination}`,
|
|
49
|
+
{ cause: error }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Unable to publish migrated SQLite database: ${error.message}`, {
|
|
53
|
+
cause: error
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function cleanupMigrationDestination(plan) {
|
|
59
|
+
if (!plan.ownsStorePath) return;
|
|
60
|
+
|
|
61
|
+
const errors = [];
|
|
62
|
+
for (const artifact of ownedArtifacts(plan.storePath)) {
|
|
63
|
+
try {
|
|
64
|
+
unlinkSync(artifact);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error.code !== 'ENOENT') errors.push(error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
removeEmptyParents(plan.createdParents, errors);
|
|
70
|
+
|
|
71
|
+
if (errors.length > 0) {
|
|
72
|
+
throw new AggregateError(errors, `Unable to clean migration temporary database: ${plan.storePath}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function reserveTemporaryPath(destination) {
|
|
77
|
+
const temporaryPath = join(
|
|
78
|
+
dirname(destination),
|
|
79
|
+
`.${basename(destination)}.${randomUUID()}.migration`
|
|
80
|
+
);
|
|
81
|
+
const descriptor = openSync(temporaryPath, 'wx');
|
|
82
|
+
closeSync(descriptor);
|
|
83
|
+
return temporaryPath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createParents(parent) {
|
|
87
|
+
const firstCreated = mkdirSync(parent, { recursive: true });
|
|
88
|
+
if (firstCreated === undefined) return [];
|
|
89
|
+
|
|
90
|
+
const parents = [];
|
|
91
|
+
const boundary = comparablePath(firstCreated);
|
|
92
|
+
let current = parent;
|
|
93
|
+
while (true) {
|
|
94
|
+
parents.push(current);
|
|
95
|
+
if (comparablePath(current) === boundary) return parents;
|
|
96
|
+
current = dirname(current);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function removeEmptyParents(parents, errors = []) {
|
|
101
|
+
for (const parent of parents) {
|
|
102
|
+
try {
|
|
103
|
+
if (readdirSync(parent).length === 0) rmdirSync(parent);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error.code !== 'ENOENT' && error.code !== 'ENOTEMPTY') errors.push(error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ownedArtifacts(storePath) {
|
|
111
|
+
return [`${storePath}-wal`, `${storePath}-shm`, storePath];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function comparablePath(path) {
|
|
115
|
+
const resolved = resolve(path).replace(/^\\\\\?\\/, '');
|
|
116
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
117
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
|
|
4
|
+
const HIDE_CURSOR = '\u001B[?25l';
|
|
5
|
+
const SHOW_CURSOR = '\u001B[?25h';
|
|
6
|
+
|
|
7
|
+
export function shouldPromptForAgentSelection(plan, options) {
|
|
8
|
+
if (options.yes || options.skipAgents || options.codexOnly) return false;
|
|
9
|
+
return availableAgents(plan).length > 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function applyAgentSelection(plan, selectedAgentIds) {
|
|
13
|
+
const selected = new Set(selectedAgentIds);
|
|
14
|
+
return {
|
|
15
|
+
...plan,
|
|
16
|
+
agents: plan.agents.map((agent) => ({
|
|
17
|
+
...agent,
|
|
18
|
+
selected: agent.available && selected.has(agent.id)
|
|
19
|
+
}))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseAgentSelection(value, agents) {
|
|
24
|
+
const answer = value.trim();
|
|
25
|
+
if (!answer) return agents.map(({ id }) => id);
|
|
26
|
+
if (answer === '0') return [];
|
|
27
|
+
|
|
28
|
+
const tokens = answer.split(/[\s,,]+/).filter(Boolean);
|
|
29
|
+
if (tokens.includes('0')) {
|
|
30
|
+
throw new TypeError('0 不能与其他编号同时使用');
|
|
31
|
+
}
|
|
32
|
+
if (tokens.some((token) => !/^\d+$/.test(token))) {
|
|
33
|
+
throw new TypeError('请输入编号,不要输入 Agent 名称');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const positions = new Set(tokens.map(Number));
|
|
37
|
+
if ([...positions].some((position) => position < 1 || position > agents.length)) {
|
|
38
|
+
throw new RangeError(`可选范围是 1-${agents.length}`);
|
|
39
|
+
}
|
|
40
|
+
return agents
|
|
41
|
+
.filter((_, index) => positions.has(index + 1))
|
|
42
|
+
.map(({ id }) => id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createAgentSelectionState(agents) {
|
|
46
|
+
return {
|
|
47
|
+
activeIndex: 0,
|
|
48
|
+
selected: agents.map(() => true)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function handleAgentSelectionKey(state, key) {
|
|
53
|
+
if (key.ctrl && key.name === 'c') return { action: 'cancel', state };
|
|
54
|
+
if (key.name === 'return' || key.name === 'enter') return { action: 'submit', state };
|
|
55
|
+
|
|
56
|
+
if (key.name === 'up' || key.name === 'down') {
|
|
57
|
+
const direction = key.name === 'up' ? -1 : 1;
|
|
58
|
+
const activeIndex = (
|
|
59
|
+
state.activeIndex + direction + state.selected.length
|
|
60
|
+
) % state.selected.length;
|
|
61
|
+
return { action: 'update', state: { ...state, activeIndex } };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (key.name === 'space' || key.name === ' ') {
|
|
65
|
+
const selected = [...state.selected];
|
|
66
|
+
selected[state.activeIndex] = !selected[state.activeIndex];
|
|
67
|
+
return { action: 'update', state: { ...state, selected } };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (key.name === 'a') {
|
|
71
|
+
const nextValue = !state.selected.every(Boolean);
|
|
72
|
+
return {
|
|
73
|
+
action: 'update',
|
|
74
|
+
state: { ...state, selected: state.selected.map(() => nextValue) }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { action: 'ignore', state };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function selectedAgentIds(agents, state) {
|
|
82
|
+
return agents
|
|
83
|
+
.filter((_, index) => state.selected[index])
|
|
84
|
+
.map(({ id }) => id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function formatAgentCheckboxSelection(agents, state) {
|
|
88
|
+
return [
|
|
89
|
+
'检测到多个可接入的 Agent:',
|
|
90
|
+
...agents.map((agent, index) => {
|
|
91
|
+
const pointer = index === state.activeIndex ? '❯' : ' ';
|
|
92
|
+
const checkbox = state.selected[index] ? '[x]' : '[ ]';
|
|
93
|
+
const status = integrationStatusLabel(agent.integrationStatus);
|
|
94
|
+
return `${pointer} ${checkbox} ${agent.label}${status ? ` ${status}` : ''}`;
|
|
95
|
+
}),
|
|
96
|
+
'↑/↓ 移动 · 空格切换 · A 全选/清空 · 回车确认'
|
|
97
|
+
].join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function integrationStatusLabel(status) {
|
|
101
|
+
if (status === 'connected') return '已接入';
|
|
102
|
+
if (status === 'update_available') return '需更新';
|
|
103
|
+
if (status === 'not_connected') return '未接入';
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function promptAgentSelection(agents, {
|
|
108
|
+
input = process.stdin,
|
|
109
|
+
output = process.stdout
|
|
110
|
+
} = {}) {
|
|
111
|
+
if (supportsCheckboxSelection(input, output)) {
|
|
112
|
+
return promptCheckboxAgentSelection(agents, { input, output });
|
|
113
|
+
}
|
|
114
|
+
return promptTextAgentSelection(agents, { input, output });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function promptCheckboxAgentSelection(agents, { input, output }) {
|
|
118
|
+
let state = createAgentSelectionState(agents);
|
|
119
|
+
const lineCount = agents.length + 2;
|
|
120
|
+
const wasPaused = typeof input.isPaused === 'function' && input.isPaused();
|
|
121
|
+
const wasRaw = input.isRaw === true;
|
|
122
|
+
emitKeypressEvents(input);
|
|
123
|
+
if (!wasRaw) input.setRawMode(true);
|
|
124
|
+
input.resume();
|
|
125
|
+
output.write(HIDE_CURSOR);
|
|
126
|
+
output.write(formatAgentCheckboxSelection(agents, state));
|
|
127
|
+
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
const finish = (callback) => {
|
|
130
|
+
input.off('keypress', onKeypress);
|
|
131
|
+
if (!wasRaw) input.setRawMode(false);
|
|
132
|
+
if (wasPaused) input.pause();
|
|
133
|
+
output.write(`${SHOW_CURSOR}\n`);
|
|
134
|
+
callback();
|
|
135
|
+
};
|
|
136
|
+
const redraw = () => {
|
|
137
|
+
output.write(`\r\u001B[${lineCount - 1}A\u001B[J`);
|
|
138
|
+
output.write(formatAgentCheckboxSelection(agents, state));
|
|
139
|
+
};
|
|
140
|
+
const onKeypress = (character, key = {}) => {
|
|
141
|
+
const result = handleAgentSelectionKey(state, {
|
|
142
|
+
...key,
|
|
143
|
+
name: key.name ?? character
|
|
144
|
+
});
|
|
145
|
+
if (result.action === 'submit') {
|
|
146
|
+
finish(() => resolve(selectedAgentIds(agents, state)));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (result.action === 'cancel') {
|
|
150
|
+
finish(() => reject(new Error('Setup 已取消')));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (result.action === 'update') {
|
|
154
|
+
state = result.state;
|
|
155
|
+
redraw();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
input.on('keypress', onKeypress);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function promptTextAgentSelection(agents, { input, output }) {
|
|
163
|
+
const terminal = createInterface({ input, output });
|
|
164
|
+
output.write('检测到多个可接入的 Agent:\n');
|
|
165
|
+
for (const [index, agent] of agents.entries()) {
|
|
166
|
+
output.write(` ${index + 1}. ${agent.label}\n`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
while (true) {
|
|
171
|
+
const answer = await terminal.question(
|
|
172
|
+
'选择要接入的 Agent(编号以逗号分隔,回车全选,0 跳过)[全部] '
|
|
173
|
+
);
|
|
174
|
+
try {
|
|
175
|
+
return parseAgentSelection(answer, agents);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
output.write(`选择无效:${error.message}\n`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} finally {
|
|
181
|
+
terminal.close();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function supportsCheckboxSelection(input, output) {
|
|
186
|
+
return Boolean(
|
|
187
|
+
input.isTTY &&
|
|
188
|
+
output.isTTY &&
|
|
189
|
+
typeof input.setRawMode === 'function'
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function availableAgents(plan) {
|
|
194
|
+
return plan.agents.filter(({ available }) => available);
|
|
195
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
applyAgentSelection,
|
|
5
|
+
promptAgentSelection,
|
|
6
|
+
shouldPromptForAgentSelection
|
|
7
|
+
} from './setup-agent-selection.js';
|
|
8
|
+
import { parseSetupOptions } from '../setup/options.js';
|
|
9
|
+
import { applyLocalSetup, planLocalSetup } from '../setup/setup.js';
|
|
10
|
+
|
|
11
|
+
export async function runSetupCommand(args, dependencies = {}) {
|
|
12
|
+
const options = parseSetupOptions(args);
|
|
13
|
+
const planSetup = dependencies.planSetup ?? planLocalSetup;
|
|
14
|
+
const applySetup = dependencies.applySetup ?? applyLocalSetup;
|
|
15
|
+
const confirm = dependencies.confirm ?? confirmInTerminal;
|
|
16
|
+
const selectAgents = dependencies.selectAgents ?? promptAgentSelection;
|
|
17
|
+
const write = dependencies.write ?? writeLine;
|
|
18
|
+
let plan = planSetup(options, dependencies);
|
|
19
|
+
if (shouldPromptForAgentSelection(plan, options)) {
|
|
20
|
+
const detected = plan.agents.filter(({ available }) => available);
|
|
21
|
+
plan = applyAgentSelection(plan, await selectAgents(detected));
|
|
22
|
+
}
|
|
23
|
+
write(formatSetupPreview(plan, options));
|
|
24
|
+
|
|
25
|
+
if (!options.yes && !await confirm()) {
|
|
26
|
+
write('已取消,没有修改任何内容。');
|
|
27
|
+
return { status: 'cancelled' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = await applySetup(plan, options, {
|
|
31
|
+
...dependencies,
|
|
32
|
+
onProgress: write
|
|
33
|
+
});
|
|
34
|
+
write(formatSetupResult(result, plan));
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatSetupPreview(plan, options) {
|
|
39
|
+
const available = plan.agents.filter(({ available }) => available);
|
|
40
|
+
const selected = available.filter(({ selected }) => selected !== false);
|
|
41
|
+
const agentSummary = options.skipAgents || (available.length > 0 && selected.length === 0)
|
|
42
|
+
? '跳过接入'
|
|
43
|
+
: selected.map(({ label }) => label).join('、') || '未检测到';
|
|
44
|
+
return [
|
|
45
|
+
'准备设置复利',
|
|
46
|
+
options.personalOnly
|
|
47
|
+
? '存储:仅个人:本机 Graphiti / Neo4j'
|
|
48
|
+
: '存储:本机 Graphiti / Neo4j + 团队共享项目 Provider',
|
|
49
|
+
'容器运行时:自动检测;已安装但未运行时会自动启动',
|
|
50
|
+
`公共服务:${options.personalOnly ? '暂不连接' : '连接本机开发 Provider'}`,
|
|
51
|
+
`个人空间:${options.personalSpaceName}`,
|
|
52
|
+
`本地控制台:${options.noStart ? '暂不启动' : `http://127.0.0.1:${options.port}`}`,
|
|
53
|
+
`Agent:${agentSummary}`
|
|
54
|
+
].join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function formatSetupResult(result, plan) {
|
|
58
|
+
const lines = [result.status === 'ready' ? '复利已准备好。' : '复利已启动,但有 Agent 未连接。'];
|
|
59
|
+
if (result.runtime.url) lines.push(`打开:${result.runtime.url}`);
|
|
60
|
+
lines.push('知识存储:Graphiti / Neo4j');
|
|
61
|
+
for (const agent of result.agents) {
|
|
62
|
+
if (agent.status !== 'connected') {
|
|
63
|
+
lines.push(`${agent.label}:连接失败,请稍后重试 setup`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
lines.push(agent.newTaskRequired
|
|
67
|
+
? `${agent.label}:已连接;请新建或重新打开一个任务以加载新配置`
|
|
68
|
+
: `${agent.label}:已连接`);
|
|
69
|
+
}
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function confirmInTerminal() {
|
|
74
|
+
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
75
|
+
try {
|
|
76
|
+
const answer = (await input.question('只会修改以上内容,继续?[Y/n] ')).trim().toLowerCase();
|
|
77
|
+
return answer === '' || answer === 'y' || answer === 'yes';
|
|
78
|
+
} finally {
|
|
79
|
+
input.close();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function writeLine(value) {
|
|
84
|
+
process.stdout.write(`${value}\n`);
|
|
85
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
|
|
3
|
+
import { FULI_PACKAGE_NAME } from '../package-metadata.js';
|
|
4
|
+
import { parseUninstallOptions } from '../setup/options.js';
|
|
5
|
+
import { applyLocalUninstall, planLocalUninstall } from '../setup/uninstall.js';
|
|
6
|
+
|
|
7
|
+
export async function runUninstallCommand(args, dependencies = {}) {
|
|
8
|
+
const options = parseUninstallOptions(args);
|
|
9
|
+
const planUninstall = dependencies.planUninstall ?? planLocalUninstall;
|
|
10
|
+
const applyUninstall = dependencies.applyUninstall ?? applyLocalUninstall;
|
|
11
|
+
const confirm = dependencies.confirm ?? confirmInTerminal;
|
|
12
|
+
const write = dependencies.write ?? writeLine;
|
|
13
|
+
const plan = planUninstall(options, dependencies);
|
|
14
|
+
write(formatUninstallPreview(plan));
|
|
15
|
+
|
|
16
|
+
if (!options.yes && !await confirm()) {
|
|
17
|
+
write('已取消,没有修改任何内容。');
|
|
18
|
+
return { status: 'cancelled' };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = await applyUninstall(plan, dependencies);
|
|
22
|
+
write(formatUninstallResult(result));
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatUninstallPreview(plan) {
|
|
27
|
+
const labels = plan.agents.map(({ label }) => label).join('、') || '无';
|
|
28
|
+
return [
|
|
29
|
+
'准备清理复利的本机接入',
|
|
30
|
+
'本地服务:停止',
|
|
31
|
+
`Agent 接入:${labels}`,
|
|
32
|
+
'Skills:仅删除与当前安装包完全一致的副本;有本地修改的会保留',
|
|
33
|
+
`数据:保留 ${plan.paths.dataDir}`,
|
|
34
|
+
'Neo4j 数据卷:保留'
|
|
35
|
+
].join('\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatUninstallResult(result) {
|
|
39
|
+
const lines = [
|
|
40
|
+
result.status === 'ready' ? '复利本机接入已清理。' : '复利本机接入未完全清理。',
|
|
41
|
+
`数据已保留:${result.data.path}`,
|
|
42
|
+
`最后移除全局命令:npm uninstall --global ${FULI_PACKAGE_NAME}`
|
|
43
|
+
];
|
|
44
|
+
if (result.runtime.status !== 'stopped') {
|
|
45
|
+
lines.push(`本地服务:${result.runtime.message ?? '未能安全确认已停止'}`);
|
|
46
|
+
}
|
|
47
|
+
for (const agent of result.agents) {
|
|
48
|
+
const modified = agent.skills.some(({ status }) => status === 'preserved_modified');
|
|
49
|
+
if (modified) lines.push(`${agent.label}:保留了有本地修改的 Skill`);
|
|
50
|
+
if (agent.errors?.length) lines.push(`${agent.label}:${agent.errors.join(';')}`);
|
|
51
|
+
}
|
|
52
|
+
return lines.join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function confirmInTerminal() {
|
|
56
|
+
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
57
|
+
try {
|
|
58
|
+
const answer = (await input.question('继续清理本机接入?[y/N] ')).trim().toLowerCase();
|
|
59
|
+
return answer === 'y' || answer === 'yes';
|
|
60
|
+
} finally {
|
|
61
|
+
input.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeLine(value) {
|
|
66
|
+
process.stdout.write(`${value}\n`);
|
|
67
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
dispatchCommand,
|
|
7
|
+
isLocalRuntimeCommand,
|
|
8
|
+
isRegisteredCommand,
|
|
9
|
+
printHelp
|
|
10
|
+
} from './cli/command-registry.js';
|
|
11
|
+
import { parseCliInvocation } from './cli/invocation.js';
|
|
12
|
+
import { runLocalRuntimeCommand } from './cli/local-runtime-command.js';
|
|
13
|
+
import { migrateLegacyJson } from './cli/migrate-command.js';
|
|
14
|
+
import { runSetupCommand } from './cli/setup-command.js';
|
|
15
|
+
import { runUninstallCommand } from './cli/uninstall-command.js';
|
|
16
|
+
import { FULI_VERSION } from './package-metadata.js';
|
|
17
|
+
import { assertSupportedNodeVersion } from './setup/node-runtime.js';
|
|
18
|
+
import {
|
|
19
|
+
openLocalApplication,
|
|
20
|
+
resolveRuntimeOptions
|
|
21
|
+
} from './runtime-options.js';
|
|
22
|
+
|
|
23
|
+
export async function main(argv = process.argv.slice(2), env = process.env) {
|
|
24
|
+
const { runtimeArgs, command, args: commandArgs } = parseCliInvocation(argv);
|
|
25
|
+
if (!command || command === '--help' || command === '-h') {
|
|
26
|
+
if (runtimeArgs.length) resolveRuntimeOptions(runtimeArgs, {});
|
|
27
|
+
printHelp();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (command === '--version' || command === '-v') {
|
|
31
|
+
if (runtimeArgs.length) resolveRuntimeOptions(runtimeArgs, {});
|
|
32
|
+
console.log(FULI_VERSION);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (command === 'migrate') {
|
|
36
|
+
if (runtimeArgs.length) resolveRuntimeOptions(runtimeArgs, {});
|
|
37
|
+
console.log(JSON.stringify(migrateLegacyJson(commandArgs)));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (command === 'setup') {
|
|
41
|
+
if (runtimeArgs.length) {
|
|
42
|
+
throw new TypeError('Setup options must appear after the setup command');
|
|
43
|
+
}
|
|
44
|
+
assertSupportedNodeVersion();
|
|
45
|
+
await runSetupCommand(commandArgs, { env });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (command === 'uninstall') {
|
|
49
|
+
if (runtimeArgs.length) {
|
|
50
|
+
throw new TypeError('Uninstall options must appear after the uninstall command');
|
|
51
|
+
}
|
|
52
|
+
await runUninstallCommand(commandArgs, { env });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (isLocalRuntimeCommand(command)) {
|
|
56
|
+
if (runtimeArgs.length) {
|
|
57
|
+
throw new TypeError(`${command} options must appear after the command`);
|
|
58
|
+
}
|
|
59
|
+
return runLocalRuntimeCommand(command, commandArgs, { env });
|
|
60
|
+
}
|
|
61
|
+
if (!isRegisteredCommand(command)) throw new Error(`Unknown command: ${command}`);
|
|
62
|
+
|
|
63
|
+
const options = resolveRuntimeOptions(runtimeArgs, env);
|
|
64
|
+
const app = openLocalApplication(options);
|
|
65
|
+
try {
|
|
66
|
+
dispatchCommand(app, command, commandArgs);
|
|
67
|
+
} finally {
|
|
68
|
+
app.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isMainModule(import.meta.url, process.argv[1])) {
|
|
73
|
+
main()
|
|
74
|
+
.then((result) => {
|
|
75
|
+
if (Number.isInteger(result?.exitCode)) process.exitCode = result.exitCode;
|
|
76
|
+
})
|
|
77
|
+
.catch((error) => {
|
|
78
|
+
console.error(error.message);
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isMainModule(metaUrl, argvPath) {
|
|
84
|
+
if (!argvPath) return false;
|
|
85
|
+
try {
|
|
86
|
+
return realpathSync(fileURLToPath(metaUrl)) === realpathSync(argvPath);
|
|
87
|
+
} catch {
|
|
88
|
+
return fileURLToPath(metaUrl) === argvPath;
|
|
89
|
+
}
|
|
90
|
+
}
|