@notis_ai/cli 0.2.0-beta.16.1 → 0.2.0-beta.160.1
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/README.md +433 -133
- package/config/notis_app_boundary_rules.json +50 -0
- package/config/notis_app_design_rules.json +135 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +18672 -0
- package/dist/base-skills/notis-apps/SKILL.md +70 -0
- package/dist/base-skills/notis-apps/references/architecture.md +164 -0
- package/dist/base-skills/notis-apps/references/context.md +81 -0
- package/dist/base-skills/notis-apps/references/design.md +165 -0
- package/dist/base-skills/notis-apps/references/reading.md +89 -0
- package/dist/base-skills/notis-apps/references/release.md +99 -0
- package/dist/base-skills/notis-apps/references/sdk.md +62 -0
- package/dist/base-skills/notis-apps/references/troubleshooting.md +23 -0
- package/dist/base-skills/notis-cli/SKILL.md +140 -0
- package/dist/base-skills/notis-cli/references/app-delivery.md +18 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +20 -0
- package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
- package/dist/base-skills/notis-cli/references/troubleshooting.md +39 -0
- package/dist/base-skills/notis-query/SKILL.md +67 -0
- package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
- package/dist/base-skills/notis-query/references/documents.md +50 -0
- package/dist/base-skills/notis-query/references/query.md +543 -0
- package/dist/skill-sync/index.js +1626 -0
- package/dist/skill-sync/index.js.map +7 -0
- package/dist/skill-sync-worker.mjs +2990 -0
- package/package.json +16 -6
- package/skills/notis-apps/cli.md +313 -0
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +39 -0
- package/skills/notis-onboarding/BRIEF.md +129 -0
- package/skills/notis-query/cli.md +39 -0
- package/src/agent-hook-entry.js +5 -0
- package/src/cli.js +294 -25
- package/src/command-specs/agents.js +392 -0
- package/src/command-specs/apps.js +1470 -202
- package/src/command-specs/auth.js +114 -137
- package/src/command-specs/diagnostics.js +716 -0
- package/src/command-specs/handover.js +374 -0
- package/src/command-specs/helpers.js +84 -82
- package/src/command-specs/index.js +25 -6
- package/src/command-specs/meta.js +150 -18
- package/src/command-specs/onboarding.js +290 -0
- package/src/command-specs/profile.js +358 -0
- package/src/command-specs/reports.js +86 -0
- package/src/command-specs/skills.js +75 -0
- package/src/command-specs/smoke.js +386 -0
- package/src/command-specs/tools.js +455 -139
- package/src/runtime/agent-browser.js +632 -0
- package/src/runtime/agent-memory-state.js +126 -0
- package/src/runtime/agent-setup.js +383 -0
- package/src/runtime/app-boundary-validator.js +404 -0
- package/src/runtime/app-changelog.js +79 -0
- package/src/runtime/app-platform.js +2633 -210
- package/src/runtime/app-registry-scaffolds.js +367 -0
- package/src/runtime/app-test-server.js +292 -0
- package/src/runtime/assets/store-screenshot-dark.png +0 -0
- package/src/runtime/auth-recovery.js +110 -0
- package/src/runtime/base-skills.d.ts +20 -0
- package/src/runtime/base-skills.js +167 -0
- package/src/runtime/channel.js +133 -0
- package/src/runtime/delegated-context.js +68 -0
- package/src/runtime/errors.js +1 -0
- package/src/runtime/git.js +233 -0
- package/src/runtime/login-listener.js +15 -0
- package/src/runtime/oauth.js +2622 -0
- package/src/runtime/output.js +37 -5
- package/src/runtime/ports.js +31 -0
- package/src/runtime/profiles.js +906 -55
- package/src/runtime/skill-sync/cloud-client.ts +99 -0
- package/src/runtime/skill-sync/index.ts +697 -0
- package/src/runtime/skill-sync/local-scanner.ts +1046 -0
- package/src/runtime/skill-sync/symlink-manager.ts +433 -0
- package/src/runtime/skill-sync/sync-plan.ts +22 -0
- package/src/runtime/skill-sync/types.ts +110 -0
- package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
- package/src/runtime/skill-sync-service.js +109 -0
- package/src/runtime/store-screenshot.js +143 -0
- package/src/runtime/sync-skills.d.ts +37 -0
- package/src/runtime/sync-skills.js +231 -0
- package/src/runtime/telemetry.js +92 -0
- package/src/runtime/transport.js +324 -45
- package/src/skill-sync-worker-entry.js +2 -0
- package/src/skill-sync-worker.js +50 -0
- package/template/.harness/index.html.tmpl +430 -0
- package/template/CHANGELOG.md +5 -0
- package/template/app/layout.tsx +5 -2
- package/template/app/page.tsx +49 -42
- package/template/components/page-heading.tsx +23 -0
- package/template/components/ui/badge.tsx +7 -4
- package/template/components/ui/card.tsx +24 -11
- package/template/components/ui/native-select.tsx +24 -0
- package/template/notis.config.ts +24 -6
- package/template/package-lock.json +4137 -0
- package/template/package.json +5 -5
- package/template/packages/{notis-sdk → sdk}/package.json +13 -3
- package/template/packages/sdk/src/agentContext.ts +36 -0
- package/template/packages/sdk/src/components/DocumentEditor.tsx +103 -0
- package/template/packages/sdk/src/components/Markdown.tsx +60 -0
- package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +285 -0
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +97 -0
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/template/packages/sdk/src/components/NotisCommentBoundary.tsx +172 -0
- package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +59 -0
- package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
- package/template/packages/sdk/src/components/Skeleton.tsx +24 -0
- package/template/packages/sdk/src/config.ts +257 -0
- package/template/packages/sdk/src/documents.ts +256 -0
- package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
- package/template/packages/sdk/src/hooks/useAgentContext.ts +23 -0
- package/template/packages/sdk/src/hooks/useCloudComputer.ts +64 -0
- package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +836 -0
- package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +49 -0
- package/template/packages/sdk/src/hooks/useDatabaseSubscription.ts +76 -0
- package/template/packages/sdk/src/hooks/useDocument.ts +43 -0
- package/template/packages/sdk/src/hooks/useDocuments.ts +84 -0
- package/template/packages/sdk/src/hooks/useHandover.ts +78 -0
- package/template/packages/sdk/src/hooks/useLongPressSelection.ts +79 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +95 -0
- package/template/packages/{notis-sdk → sdk}/src/hooks/useNotis.ts +10 -4
- package/template/packages/{notis-sdk → sdk}/src/hooks/useNotisNavigation.ts +11 -8
- package/template/packages/sdk/src/hooks/useQuery.ts +71 -0
- package/template/packages/sdk/src/hooks/useTool.ts +65 -0
- package/template/packages/sdk/src/hooks/useToolQuery.ts +12 -0
- package/template/packages/sdk/src/hooks/useTopBarSearch.ts +81 -0
- package/template/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/template/packages/sdk/src/index.ts +161 -0
- package/template/packages/sdk/src/interactions/actions.ts +59 -0
- package/template/packages/sdk/src/interactions/shortcuts.tsx +694 -0
- package/template/packages/sdk/src/interactions/visibility.ts +13 -0
- package/template/packages/sdk/src/interactions.ts +45 -0
- package/template/packages/sdk/src/provider.tsx +44 -0
- package/template/packages/sdk/src/queryCache.ts +170 -0
- package/template/packages/sdk/src/runtime.ts +451 -0
- package/template/packages/sdk/src/styles.css +213 -0
- package/template/packages/sdk/src/tailwind.ts +56 -0
- package/template/packages/{notis-sdk → sdk}/src/vite.ts +5 -1
- package/template/tailwind.config.ts +1 -0
- package/src/command-specs/db.js +0 -163
- package/src/runtime/app-preview-server.js +0 -312
- package/template/packages/notis-sdk/src/config.ts +0 -48
- package/template/packages/notis-sdk/src/helpers.ts +0 -131
- package/template/packages/notis-sdk/src/hooks/useAppState.ts +0 -50
- package/template/packages/notis-sdk/src/hooks/useCollectionItem.ts +0 -58
- package/template/packages/notis-sdk/src/hooks/useDatabase.ts +0 -87
- package/template/packages/notis-sdk/src/hooks/useDocument.ts +0 -61
- package/template/packages/notis-sdk/src/hooks/useTool.ts +0 -49
- package/template/packages/notis-sdk/src/hooks/useUpsertDocument.ts +0 -57
- package/template/packages/notis-sdk/src/index.ts +0 -47
- package/template/packages/notis-sdk/src/provider.tsx +0 -44
- package/template/packages/notis-sdk/src/runtime.ts +0 -159
- package/template/packages/notis-sdk/src/styles.css +0 -123
- /package/template/packages/{notis-sdk → sdk}/src/hooks/useBackend.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/hooks/useTools.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/ui.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/tsconfig.json +0 -0
package/src/cli.js
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { Command, Option } from 'commander';
|
|
2
6
|
import { COMMAND_SPECS, GROUP_SUMMARIES } from './command-specs/index.js';
|
|
3
7
|
import { OutputManager } from './runtime/output.js';
|
|
4
8
|
import { asCliError } from './runtime/errors.js';
|
|
5
|
-
import {
|
|
9
|
+
import { reportCliCommand } from './runtime/telemetry.js';
|
|
10
|
+
import { reconcileBaseSkillsBestEffort } from './runtime/base-skills.js';
|
|
11
|
+
import { detectedAgentIds } from './runtime/agent-setup.js';
|
|
12
|
+
import { withSkillSyncLock } from './runtime/sync-skills.js';
|
|
13
|
+
import { maybeInstallSkillSyncService } from './runtime/skill-sync-service.js';
|
|
14
|
+
import {
|
|
15
|
+
CHANNEL_SWITCH_ENV,
|
|
16
|
+
resolveChannelSwitch,
|
|
17
|
+
} from './runtime/channel.js';
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_PROFILE,
|
|
20
|
+
getProfile,
|
|
21
|
+
loadConfig,
|
|
22
|
+
resolveOutputMode,
|
|
23
|
+
resolveRuntimeProfile,
|
|
24
|
+
resolveWorktreeRuntime,
|
|
25
|
+
workspacePath,
|
|
26
|
+
} from './runtime/profiles.js';
|
|
6
27
|
|
|
7
|
-
|
|
28
|
+
// Read the version from package.json: the publish pipeline bumps the manifest
|
|
29
|
+
// (scripts/release-utils.js applyVersion), so a hardcoded string here goes
|
|
30
|
+
// stale on every release.
|
|
31
|
+
function readCliVersion() {
|
|
32
|
+
try {
|
|
33
|
+
const manifestPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
34
|
+
const version = JSON.parse(readFileSync(manifestPath, 'utf-8')).version;
|
|
35
|
+
if (typeof version === 'string' && version.trim()) {
|
|
36
|
+
return version.trim();
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// Fall through to the placeholder below.
|
|
40
|
+
}
|
|
41
|
+
return '0.0.0';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const CLI_VERSION = readCliVersion();
|
|
8
45
|
|
|
9
46
|
function buildHelpFooter(spec) {
|
|
10
47
|
const lines = [
|
|
@@ -44,19 +81,70 @@ function ensureParentCommand(program, parentMap, parentPath) {
|
|
|
44
81
|
}
|
|
45
82
|
|
|
46
83
|
function buildRuntime(globalOptions, spec) {
|
|
47
|
-
const
|
|
84
|
+
const localBackendTypes = new Set(['local', 'local_config', 'local_registry']);
|
|
85
|
+
const commandName = spec.command_path.join(' ');
|
|
86
|
+
const liveAppHarness = (
|
|
87
|
+
commandName === 'apps verify' || commandName === 'apps screenshot'
|
|
88
|
+
) && globalOptions.mode === 'live';
|
|
89
|
+
const runtime = resolveRuntimeProfile(globalOptions, {
|
|
90
|
+
requireAuth: spec.require_auth !== false,
|
|
91
|
+
allowUnknownProfile: spec.allow_unknown_profile === true,
|
|
92
|
+
allowUnavailableWorktree:
|
|
93
|
+
localBackendTypes.has(spec.backend_call?.type) && !liveAppHarness,
|
|
94
|
+
});
|
|
95
|
+
// A stopped local-only worktree may keep executing commands whose backend is
|
|
96
|
+
// entirely local, but it must not carry a shared live credential into those
|
|
97
|
+
// handlers or their post-command telemetry. Naming --profile explicitly is
|
|
98
|
+
// the deliberate escape hatch from the worktree boundary.
|
|
99
|
+
if (runtime.worktreeRuntimeUnavailable && runtime.profileSource !== 'explicit') {
|
|
100
|
+
runtime.jwt = undefined;
|
|
101
|
+
runtime.credentialKind = undefined;
|
|
102
|
+
runtime.credentialSource = undefined;
|
|
103
|
+
runtime.oauthAccessToken = undefined;
|
|
104
|
+
runtime.oauthRefreshToken = undefined;
|
|
105
|
+
}
|
|
48
106
|
return {
|
|
49
107
|
...runtime,
|
|
50
108
|
cliVersion: CLI_VERSION,
|
|
51
109
|
color: globalOptions.color !== false,
|
|
110
|
+
quiet: Boolean(globalOptions.quiet),
|
|
111
|
+
verbose: Boolean(globalOptions.verbose),
|
|
52
112
|
workspacePath,
|
|
53
113
|
};
|
|
54
114
|
}
|
|
55
115
|
|
|
56
|
-
function
|
|
116
|
+
function buildErrorRuntime(globalOptions) {
|
|
117
|
+
try {
|
|
118
|
+
return {
|
|
119
|
+
...resolveRuntimeProfile(globalOptions, {
|
|
120
|
+
requireAuth: false,
|
|
121
|
+
includeDebugEntitlementOverride: false,
|
|
122
|
+
allowUnknownProfile: true,
|
|
123
|
+
}),
|
|
124
|
+
cliVersion: CLI_VERSION,
|
|
125
|
+
color: globalOptions.color !== false,
|
|
126
|
+
quiet: Boolean(globalOptions.quiet),
|
|
127
|
+
verbose: Boolean(globalOptions.verbose),
|
|
128
|
+
workspacePath,
|
|
129
|
+
};
|
|
130
|
+
} catch {
|
|
131
|
+
return {
|
|
132
|
+
profileName: globalOptions.profile || DEFAULT_PROFILE,
|
|
133
|
+
apiBase: null,
|
|
134
|
+
outputMode: resolveOutputMode(globalOptions),
|
|
135
|
+
cliVersion: CLI_VERSION,
|
|
136
|
+
color: globalOptions.color !== false,
|
|
137
|
+
quiet: Boolean(globalOptions.quiet),
|
|
138
|
+
verbose: Boolean(globalOptions.verbose),
|
|
139
|
+
workspacePath,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function attachSpec(program, parentMap, spec, specs, launchContext = {}) {
|
|
57
145
|
const parent = ensureParentCommand(program, parentMap, spec.command_path.slice(0, -1));
|
|
58
146
|
const leaf = spec.command_path[spec.command_path.length - 1];
|
|
59
|
-
const command = parent.command(leaf).description(spec.summary);
|
|
147
|
+
const command = parent.command(leaf, spec.hidden ? { hidden: true } : undefined).description(spec.summary);
|
|
60
148
|
command.addHelpText('after', buildHelpFooter(spec));
|
|
61
149
|
|
|
62
150
|
for (const argument of spec.args_schema?.arguments || []) {
|
|
@@ -64,10 +152,20 @@ function attachSpec(program, parentMap, spec, specs) {
|
|
|
64
152
|
}
|
|
65
153
|
|
|
66
154
|
for (const option of spec.args_schema?.options || []) {
|
|
67
|
-
|
|
155
|
+
if (option.collect) {
|
|
156
|
+
command.option(
|
|
157
|
+
option.flags,
|
|
158
|
+
option.description,
|
|
159
|
+
(value, previous) => [...(previous || []), value],
|
|
160
|
+
[],
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
command.option(option.flags, option.description);
|
|
164
|
+
}
|
|
68
165
|
}
|
|
69
166
|
|
|
70
167
|
command.action(async (...raw) => {
|
|
168
|
+
const startedAt = Date.now();
|
|
71
169
|
const commanderCommand = raw[raw.length - 1];
|
|
72
170
|
const argumentValues = raw.slice(0, -1);
|
|
73
171
|
const globalOptions = commanderCommand.optsWithGlobals();
|
|
@@ -88,17 +186,30 @@ function attachSpec(program, parentMap, spec, specs) {
|
|
|
88
186
|
globalOptions,
|
|
89
187
|
runtime,
|
|
90
188
|
output,
|
|
189
|
+
...launchContext,
|
|
91
190
|
});
|
|
92
191
|
process.exitCode = typeof exitCode === 'number' ? exitCode : 0;
|
|
192
|
+
if (process.exitCode === 0 && !['logout', 'agent-context', 'agent-capture'].includes(spec.command_path[0])) {
|
|
193
|
+
await maybeInstallSkillSyncService(runtime).catch(() => undefined);
|
|
194
|
+
}
|
|
195
|
+
await reportCliCommand({
|
|
196
|
+
spec,
|
|
197
|
+
runtime,
|
|
198
|
+
result: process.exitCode === 0 ? 'success' : 'failed',
|
|
199
|
+
durationMs: Date.now() - startedAt,
|
|
200
|
+
error: process.exitCode === 0 ? null : { exitCode: process.exitCode },
|
|
201
|
+
});
|
|
93
202
|
} catch (error) {
|
|
94
|
-
const runtime =
|
|
95
|
-
...resolveRuntimeProfile(globalOptions, { requireAuth: false }),
|
|
96
|
-
cliVersion: CLI_VERSION,
|
|
97
|
-
color: globalOptions.color !== false,
|
|
98
|
-
workspacePath,
|
|
99
|
-
};
|
|
203
|
+
const runtime = buildErrorRuntime(globalOptions);
|
|
100
204
|
const output = new OutputManager(runtime);
|
|
101
205
|
const cliError = asCliError(error);
|
|
206
|
+
await reportCliCommand({
|
|
207
|
+
spec,
|
|
208
|
+
runtime,
|
|
209
|
+
result: 'failed',
|
|
210
|
+
durationMs: Date.now() - startedAt,
|
|
211
|
+
error: cliError,
|
|
212
|
+
});
|
|
102
213
|
process.exitCode = output.emitError({
|
|
103
214
|
command: spec.command_path.join(' '),
|
|
104
215
|
error: cliError,
|
|
@@ -107,12 +218,12 @@ function attachSpec(program, parentMap, spec, specs) {
|
|
|
107
218
|
});
|
|
108
219
|
}
|
|
109
220
|
|
|
110
|
-
export function createProgram() {
|
|
221
|
+
export function createProgram(launchContext = {}) {
|
|
111
222
|
const program = new Command();
|
|
112
223
|
|
|
113
224
|
program
|
|
114
225
|
.name('notis')
|
|
115
|
-
.description('Agent-first Notis CLI for apps
|
|
226
|
+
.description('Agent-first Notis CLI for apps and generic tool execution')
|
|
116
227
|
.version(CLI_VERSION)
|
|
117
228
|
.showHelpAfterError()
|
|
118
229
|
.option('--json', 'Shortcut for --output json')
|
|
@@ -121,27 +232,185 @@ export function createProgram() {
|
|
|
121
232
|
.option('--quiet', 'Suppress non-essential human output')
|
|
122
233
|
.option('--verbose', 'Show extra human-readable diagnostics')
|
|
123
234
|
.option('--no-color', 'Disable ANSI color output')
|
|
124
|
-
|
|
235
|
+
// No default value: commander cannot tell a default apart from an explicit
|
|
236
|
+
// flag, and a defaulted "default" here silently overrode every
|
|
237
|
+
// `notis profile use`, pinning all commands to the default profile.
|
|
238
|
+
.option('--profile <name>', 'CLI profile to run as (defaults to the active profile)')
|
|
125
239
|
.option('--api-base <url>', 'Override the API base URL for this invocation')
|
|
126
240
|
.option('--timeout-ms <n>', 'HTTP timeout in milliseconds')
|
|
127
|
-
.option('--idempotency-key <key>', 'Override the generated idempotency key for mutating commands')
|
|
241
|
+
.option('--idempotency-key <key>', 'Override the generated idempotency key for mutating commands')
|
|
242
|
+
.addOption(new Option('--notis-managed-agent-hook').hideHelp());
|
|
128
243
|
|
|
129
244
|
const parentMap = new Map([['', program]]);
|
|
130
245
|
for (const spec of COMMAND_SPECS) {
|
|
131
|
-
attachSpec(program, parentMap, spec, COMMAND_SPECS);
|
|
246
|
+
attachSpec(program, parentMap, spec, COMMAND_SPECS, launchContext);
|
|
132
247
|
}
|
|
133
248
|
|
|
134
249
|
return program;
|
|
135
250
|
}
|
|
136
251
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Read the two global flags that decide which build should serve this run.
|
|
254
|
+
*
|
|
255
|
+
* Commander cannot help here: the decision has to be made before the program
|
|
256
|
+
* parses, because the answer may be to hand the whole invocation to a
|
|
257
|
+
* different process. Only `--profile` and `--api-base` matter, and both are
|
|
258
|
+
* plain `--flag value` pairs.
|
|
259
|
+
*/
|
|
260
|
+
export function readChannelRelevantFlags(args = []) {
|
|
261
|
+
const flags = {};
|
|
262
|
+
for (const [index, token] of args.entries()) {
|
|
263
|
+
for (const [flag, key] of [['--profile', 'profile'], ['--api-base', 'apiBase']]) {
|
|
264
|
+
if (token === flag) flags[key] = args[index + 1];
|
|
265
|
+
else if (token.startsWith(`${flag}=`)) flags[key] = token.slice(flag.length + 1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return flags;
|
|
140
269
|
}
|
|
141
270
|
|
|
142
|
-
|
|
143
|
-
|
|
271
|
+
export function channelProfileForArgs(
|
|
272
|
+
args = [],
|
|
273
|
+
env = process.env,
|
|
274
|
+
{ config: suppliedConfig, worktreeRuntime: suppliedWorktreeRuntime } = {},
|
|
275
|
+
) {
|
|
276
|
+
// An explicit endpoint overrides the stored profile for this run, so it also
|
|
277
|
+
// decides the build: `--api-base https://api-beta.notis.ai` on a production
|
|
278
|
+
// profile is a deliberate one-off beta call.
|
|
279
|
+
const { profile: profileName, apiBase } = readChannelRelevantFlags(args);
|
|
280
|
+
if (apiBase) {
|
|
281
|
+
return { api_base: apiBase };
|
|
282
|
+
}
|
|
283
|
+
if (env.NOTIS_API_BASE) {
|
|
284
|
+
return { api_base: env.NOTIS_API_BASE };
|
|
285
|
+
}
|
|
286
|
+
const config = suppliedConfig || loadConfig();
|
|
287
|
+
const explicitProfile = profileName || env.NOTIS_PROFILE;
|
|
288
|
+
if (explicitProfile) {
|
|
289
|
+
return getProfile(config, explicitProfile);
|
|
290
|
+
}
|
|
291
|
+
const resolvedWorktree = suppliedWorktreeRuntime === undefined
|
|
292
|
+
? resolveWorktreeRuntime()
|
|
293
|
+
: suppliedWorktreeRuntime;
|
|
294
|
+
// A stopped local-only worktree must reach the normal routing error on the
|
|
295
|
+
// current build. Switching based on an unrelated shared profile would escape
|
|
296
|
+
// the worktree boundary before that fail-closed check runs.
|
|
297
|
+
if (resolvedWorktree?.unavailable) {
|
|
298
|
+
return {};
|
|
299
|
+
}
|
|
300
|
+
if (resolvedWorktree?.profile) {
|
|
301
|
+
return resolvedWorktree;
|
|
302
|
+
}
|
|
303
|
+
// NOTIS_JWT replaces the credential, not the route. Unless NOTIS_API_BASE
|
|
304
|
+
// was explicit above, the selected/current profile still owns the channel.
|
|
305
|
+
return getProfile(config, config.current_profile || DEFAULT_PROFILE);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function interruptedExitCode(signal) {
|
|
309
|
+
if (signal === 'SIGINT') return 130;
|
|
310
|
+
if (signal === 'SIGTERM') return 143;
|
|
311
|
+
return 1;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function executeChannelSwitch(
|
|
315
|
+
decision,
|
|
316
|
+
args,
|
|
317
|
+
env,
|
|
318
|
+
{ spawn = spawnSync } = {},
|
|
319
|
+
) {
|
|
320
|
+
const childEnv = { ...env, [CHANNEL_SWITCH_ENV]: '1' };
|
|
321
|
+
// First make npm resolve and boot the target package without executing the
|
|
322
|
+
// requested command. That distinguishes an unavailable tag/network from a
|
|
323
|
+
// legitimate non-zero exit of the handed-off CLI, which must be propagated
|
|
324
|
+
// rather than retried locally after a possible mutation.
|
|
325
|
+
const probe = spawn(decision.command, [...decision.args, '--version'], {
|
|
326
|
+
stdio: 'ignore',
|
|
327
|
+
env: childEnv,
|
|
328
|
+
});
|
|
329
|
+
if (probe.signal) {
|
|
330
|
+
return {
|
|
331
|
+
...decision,
|
|
332
|
+
exitCode: interruptedExitCode(probe.signal),
|
|
333
|
+
reason: 'switch_interrupted',
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
if (probe.error || probe.status !== 0) {
|
|
337
|
+
return { ...decision, switch: false, reason: 'switch_unavailable' };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const result = spawn(decision.command, [...decision.args, ...args], {
|
|
341
|
+
stdio: 'inherit',
|
|
342
|
+
env: childEnv,
|
|
343
|
+
});
|
|
344
|
+
if (result.signal) {
|
|
345
|
+
return {
|
|
346
|
+
...decision,
|
|
347
|
+
exitCode: interruptedExitCode(result.signal),
|
|
348
|
+
reason: 'switch_interrupted',
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (result.error || typeof result.status !== 'number') {
|
|
352
|
+
return { ...decision, switch: false, reason: 'switch_failed' };
|
|
353
|
+
}
|
|
354
|
+
return { ...decision, exitCode: result.status };
|
|
355
|
+
}
|
|
144
356
|
|
|
145
|
-
|
|
146
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Hand this invocation to the published build the profile is pinned to.
|
|
359
|
+
*
|
|
360
|
+
* Returns the decision rather than exiting so tests can assert on it. Any
|
|
361
|
+
* failure to reach the other build is non-fatal: running the wrong channel is
|
|
362
|
+
* a much smaller problem than refusing to run at all.
|
|
363
|
+
*/
|
|
364
|
+
export function switchChannelIfNeeded(
|
|
365
|
+
argv = process.argv,
|
|
366
|
+
env = process.env,
|
|
367
|
+
{ spawn = spawnSync, platform = process.platform, moduleDirectory } = {},
|
|
368
|
+
) {
|
|
369
|
+
const args = argv.slice(2);
|
|
370
|
+
let profile;
|
|
371
|
+
try {
|
|
372
|
+
profile = channelProfileForArgs(args, env);
|
|
373
|
+
} catch {
|
|
374
|
+
return { switch: false, reason: 'profile_unreadable' };
|
|
375
|
+
}
|
|
376
|
+
const decision = resolveChannelSwitch({
|
|
377
|
+
runningVersion: CLI_VERSION,
|
|
378
|
+
profile,
|
|
379
|
+
moduleDirectory: moduleDirectory || dirname(fileURLToPath(import.meta.url)),
|
|
380
|
+
env,
|
|
381
|
+
platform,
|
|
382
|
+
});
|
|
383
|
+
if (!decision.switch) {
|
|
384
|
+
return decision;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const result = executeChannelSwitch(decision, args, env, { spawn });
|
|
388
|
+
if (!result.switch) {
|
|
389
|
+
process.stderr.write(
|
|
390
|
+
`Notis CLI could not start the ${decision.targetChannel} build for this profile; `
|
|
391
|
+
+ `continuing on ${decision.runningChannel}.\n`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function isSkillsSyncInvocation(args) {
|
|
398
|
+
return args.some((value, index) => value === 'skills' && args[index + 1] === 'sync');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export async function run(argv = process.argv) {
|
|
402
|
+
const switched = switchChannelIfNeeded(argv);
|
|
403
|
+
if (switched.switch) {
|
|
404
|
+
process.exitCode = switched.exitCode;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const preexistingAgentIds = detectedAgentIds();
|
|
408
|
+
// Every actual CLI launch repairs the three system skills before doing any
|
|
409
|
+
// command work, including before an authenticated command can fail. Capture
|
|
410
|
+
// vendor presence first because reconciliation creates their skill roots.
|
|
411
|
+
const basePreflightResult = await withSkillSyncLock(
|
|
412
|
+
async () => reconcileBaseSkillsBestEffort(),
|
|
413
|
+
).catch(() => undefined);
|
|
414
|
+
const program = createProgram({ preexistingAgentIds, basePreflightResult });
|
|
415
|
+
await program.parseAsync(argv);
|
|
147
416
|
}
|