@notis_ai/cli 0.2.0-beta.178.1 → 0.2.0-beta.1792022492092.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 +548 -98
- package/config/portal-route-roots.json +47 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +32901 -5934
- package/dist/base-skills/notis-apps/SKILL.md +76 -51
- package/dist/base-skills/notis-apps/references/architecture.md +116 -164
- package/dist/base-skills/notis-apps/references/design.md +26 -107
- package/dist/base-skills/notis-apps/references/memory.md +58 -0
- package/dist/base-skills/notis-apps/references/reading.md +52 -74
- package/dist/base-skills/notis-apps/references/release.md +105 -96
- package/dist/base-skills/notis-apps/references/sdk.md +115 -60
- package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
- package/dist/base-skills/notis-apps/references/views.md +145 -0
- package/dist/base-skills/notis-cli/SKILL.md +29 -12
- package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
- package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +6 -5
- package/dist/base-skills/notis-cli/references/tool-examples.md +4 -4
- package/dist/base-skills/notis-query/SKILL.md +34 -60
- package/dist/base-skills/notis-query/references/database-discovery.md +60 -59
- package/dist/base-skills/notis-query/references/documents.md +53 -39
- package/dist/base-skills/notis-query/references/query.md +57 -524
- package/dist/sdk/src/bundleModule.ts +79 -0
- package/dist/sdk/src/components/DocumentEditor.tsx +6 -3
- package/dist/sdk/src/components/DocumentPage.tsx +144 -0
- package/dist/sdk/src/components/RecordComponents.tsx +33 -0
- package/dist/sdk/src/components/RenderChartData.tsx +19 -0
- package/dist/sdk/src/config.ts +4 -26
- package/dist/sdk/src/documents.ts +2 -0
- package/dist/sdk/src/hooks/useAction.ts +28 -0
- package/dist/sdk/src/hooks/useBackend.ts +3 -2
- package/dist/sdk/src/hooks/useCloudComputer.ts +35 -4
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/dist/sdk/src/hooks/useCollectionInteractions.ts +4 -2
- package/dist/sdk/src/hooks/useHandover.ts +7 -7
- package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
- package/dist/sdk/src/hooks/useNativeDocuments.ts +83 -0
- package/dist/sdk/src/hooks/useNotis.ts +3 -0
- package/dist/sdk/src/hooks/useNotisNavigation.ts +26 -11
- package/dist/sdk/src/hooks/useQuery.ts +7 -4
- package/dist/sdk/src/hooks/useViewParams.ts +121 -0
- package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
- package/dist/sdk/src/index.ts +36 -0
- package/dist/sdk/src/interactions/shortcuts.tsx +42 -5
- package/dist/sdk/src/nativeData.ts +39 -0
- package/dist/sdk/src/presentation.tsx +82 -0
- package/dist/sdk/src/provider.tsx +9 -4
- package/dist/sdk/src/queryCache.ts +5 -3
- package/dist/sdk/src/queryErrors.ts +51 -0
- package/dist/sdk/src/runtime.ts +163 -13
- package/dist/sdk/src/space.ts +425 -0
- package/dist/sdk/src/styles.css +96 -72
- package/dist/sdk/src/vite.ts +11 -1
- package/dist/skill-sync/index.js +571 -80
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +858 -233
- package/dist/space-harness/host.js +66 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +1028 -0
- package/dist/view-renderer/dist/theme.css +73 -0
- package/dist/view-renderer/src/dom.js +180 -0
- package/dist/view-renderer/src/errors.js +8 -0
- package/dist/view-renderer/src/host.tsx +175 -0
- package/dist/view-renderer/src/index.js +4 -0
- package/dist/view-renderer/src/pool.js +76 -0
- package/dist/view-renderer/src/remote-broker.js +59 -0
- package/dist/view-renderer/src/renderer.js +201 -0
- package/package.json +16 -5
- package/skills/notis-apps/cli.md +323 -163
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +3 -3
- package/skills/notis-onboarding/BRIEF.md +7 -3
- package/src/command-specs/apps.js +2 -1
- package/src/command-specs/auth.js +2 -2
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/handover.js +331 -79
- package/src/command-specs/helpers.js +2 -0
- package/src/command-specs/index.js +11 -5
- package/src/command-specs/profile.js +42 -1
- package/src/command-specs/skills.js +242 -1
- package/src/command-specs/spaces.js +645 -0
- package/src/command-specs/tools.js +0 -6
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +92 -29
- package/src/runtime/app-boundary-validator.js +7 -4
- package/src/runtime/app-data-literals.js +82 -0
- package/src/runtime/app-platform.js +321 -253
- package/src/runtime/computer-identity.d.ts +30 -0
- package/src/runtime/computer-identity.js +151 -0
- package/src/runtime/config-lock.js +135 -0
- package/src/runtime/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/delegated-context.js +5 -21
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/oauth.js +18 -1
- package/src/runtime/profiles.js +5 -107
- package/src/runtime/retired-sdk-files.js +20 -0
- package/src/runtime/screenshot-framing.js +85 -0
- package/src/runtime/skill-sync/cloud-client.ts +26 -4
- package/src/runtime/skill-sync/index.ts +230 -35
- package/src/runtime/skill-sync/local-scanner.ts +188 -29
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +70 -0
- package/src/runtime/skill-sync/sync-plan.ts +16 -9
- package/src/runtime/skill-sync/types.ts +32 -6
- package/src/runtime/source-root.js +32 -0
- package/src/runtime/space-collection-scaffold.js +66 -0
- package/src/runtime/space-database-structures.js +229 -0
- package/src/runtime/space-fixture-broker.js +50 -0
- package/src/runtime/space-lock.js +337 -0
- package/src/runtime/space-platform.js +105 -0
- package/src/runtime/space-source.js +281 -0
- package/src/runtime/space-test-server.js +327 -0
- package/src/runtime/space-verification.js +89 -0
- package/src/runtime/space-verify-browser-worker.js +51 -0
- package/src/runtime/space-view-manifest.js +254 -0
- package/src/runtime/store-screenshot.js +10 -7
- package/src/runtime/sync-skills.d.ts +3 -1
- package/src/runtime/sync-skills.js +10 -2
- package/src/runtime/transport.js +8 -6
- package/src/runtime/view-render.js +49 -0
- package/src/skill-sync-worker.js +14 -2
- package/src/space-harness-host.ts +103 -0
- package/template/.harness/index.html.tmpl +7 -10
- package/template/app/globals.css +1 -0
- package/src/command-specs/reports.js +0 -97
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { CHANNEL_SWITCH_ENV } from '../runtime/channel.js';
|
|
1
3
|
import { CliError, EXIT_CODES } from '../runtime/errors.js';
|
|
2
4
|
import { quoteShellArgument } from '../runtime/auth-recovery.js';
|
|
3
5
|
import {
|
|
@@ -114,6 +116,44 @@ async function listHandler(ctx) {
|
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
const SKILL_SYNC_API_BASE_PATTERN = /^https:\/\/api(?:-beta)?\.notis\.ai\/?$/;
|
|
120
|
+
|
|
121
|
+
// Overrides that belong to this `profile use` run. Inherited, a NOTIS_JWT would
|
|
122
|
+
// sync its own account into the agent folders under the named profile.
|
|
123
|
+
const RELINK_DROPPED_ENV = ['NOTIS_JWT', 'NOTIS_API_BASE', 'NOTIS_PROFILE', CHANNEL_SWITCH_ENV];
|
|
124
|
+
|
|
125
|
+
// The new default's skills take over the agent folders (Claude Code, Codex,
|
|
126
|
+
// Cursor) now rather than at the next automatic sync. Detached, so the switch
|
|
127
|
+
// itself stays instant; a failure waits for that next sync.
|
|
128
|
+
export function relinkSkillsInBackground(name, profile, {
|
|
129
|
+
env = process.env,
|
|
130
|
+
argv = process.argv,
|
|
131
|
+
execPath = process.execPath,
|
|
132
|
+
spawnImpl = spawn,
|
|
133
|
+
} = {}) {
|
|
134
|
+
// Never from a test run: a detached sync would outlive it and reach the real API.
|
|
135
|
+
if (env.NODE_ENV === 'test' || env.NODE_TEST_CONTEXT || !argv[1]) return false;
|
|
136
|
+
if (!profile?.oauth_access_token && !profile?.oauth_refresh_token) return false;
|
|
137
|
+
if (!SKILL_SYNC_API_BASE_PATTERN.test(String(profile.api_base || ''))) return false;
|
|
138
|
+
// Like the automatic sync, the relink runs as the named profile alone: its
|
|
139
|
+
// credential, its endpoint and the CLI build for its channel.
|
|
140
|
+
const childEnv = { ...env };
|
|
141
|
+
for (const key of RELINK_DROPPED_ENV) delete childEnv[key];
|
|
142
|
+
try {
|
|
143
|
+
const child = spawnImpl(
|
|
144
|
+
execPath,
|
|
145
|
+
[argv[1], 'skills', 'sync', '--json', '--profile', name],
|
|
146
|
+
{ detached: true, stdio: 'ignore', env: childEnv },
|
|
147
|
+
);
|
|
148
|
+
child.on('error', () => {});
|
|
149
|
+
child.unref();
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
152
|
+
// The next automatic sync relinks.
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
117
157
|
async function useHandler(ctx) {
|
|
118
158
|
const requested = ctx.args.name;
|
|
119
159
|
const config = loadConfig();
|
|
@@ -132,6 +172,7 @@ async function useHandler(ctx) {
|
|
|
132
172
|
const worktreeOverride = Boolean(worktreeRuntime);
|
|
133
173
|
const worktreeBlocked = Boolean(worktreeUnavailable);
|
|
134
174
|
const authenticated = profileHasCredential(profile);
|
|
175
|
+
if (authenticated) relinkSkillsInBackground(requested, profile);
|
|
135
176
|
|
|
136
177
|
return ctx.output.emitSuccess({
|
|
137
178
|
command: ctx.spec.command_path.join(' '),
|
|
@@ -298,7 +339,7 @@ export const profileCommandSpecs = [
|
|
|
298
339
|
command_path: ['profile', 'use'],
|
|
299
340
|
summary: 'Switch the default profile without signing any profile out.',
|
|
300
341
|
when_to_use:
|
|
301
|
-
'Use this to change which account and API subsequent commands target. Every other profile keeps its credential.',
|
|
342
|
+
'Use this to change which account and API subsequent commands target. Every other profile keeps its credential. Its skills replace the previous default\'s in the agent folders (Claude Code, Codex, Cursor) straight away.',
|
|
302
343
|
args_schema: {
|
|
303
344
|
arguments: [{ token: '<name>', key: 'name', description: 'Profile to make active.' }],
|
|
304
345
|
options: [],
|
|
@@ -1,12 +1,96 @@
|
|
|
1
|
-
import { getJwtSubject } from '../runtime/profiles.js';
|
|
1
|
+
import { getCurrentProfileName, getJwtSubject, loadConfig } from '../runtime/profiles.js';
|
|
2
2
|
import { reconcileAllSkills } from '../runtime/sync-skills.js';
|
|
3
3
|
import { ensureFreshOAuthCredential } from '../runtime/oauth.js';
|
|
4
4
|
import { installSkillSyncService } from '../runtime/skill-sync-service.js';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { httpRequest } from '../runtime/transport.js';
|
|
8
|
+
import { usageError } from '../runtime/errors.js';
|
|
9
|
+
import { parseJson } from './helpers.js';
|
|
10
|
+
|
|
11
|
+
async function nativeSkillAuthoring(ctx, body, path = '/portal_skills/native-authoring') {
|
|
12
|
+
// A Space target, destination or link change touches Spaces: declare the protocol.
|
|
13
|
+
const spaceTarget = Boolean(body.target?.space_id || body.destination?.space_id || body.links);
|
|
14
|
+
const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path, body,
|
|
15
|
+
...(spaceTarget ? { spacesProtocol: 1 } : {}) });
|
|
16
|
+
return ctx.output.emitSuccess({ command: ctx.spec.command_path.join(' '), data: result.payload,
|
|
17
|
+
humanSummary: JSON.stringify(result.payload, null, 2),
|
|
18
|
+
meta: { mutating: body.operation !== 'read' && !body.dry_run, request_id: result.requestId } });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function exactKeys(value, keys) {
|
|
22
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
23
|
+
&& Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function skillTarget(value, updating) {
|
|
27
|
+
const direct = updating ? ['skill_id', 'access_revision'] : ['skill_id'];
|
|
28
|
+
const space = updating ? ['space_id', 'binding_id', 'binding_revision', 'expected_skill_id', 'space_revision'] : ['space_id', 'binding_id'];
|
|
29
|
+
if (!exactKeys(value, direct) && !exactKeys(value, space)) {
|
|
30
|
+
throw usageError('Use one exact direct or Space target from the Skill inventory or skills read.');
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestKey(ctx) {
|
|
36
|
+
const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
|
|
37
|
+
if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
|
|
38
|
+
return request;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function linkLabel(link) {
|
|
42
|
+
return `${link.space_name || link.space_id}/${link.alias}${link.available === false ? ' (unavailable)' : ''}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One line per Skill: name, ID, kind, editability and its Space links. */
|
|
46
|
+
export function skillInventoryLines(page) {
|
|
47
|
+
const entries = Array.isArray(page?.skills) ? page.skills : [];
|
|
48
|
+
if (!entries.length) return 'No Skills match.';
|
|
49
|
+
const lines = entries.map(entry => {
|
|
50
|
+
const state = entry.editable ? 'editable' : `read-only${entry.read_only_reason ? ` (${entry.read_only_reason})` : ''}`;
|
|
51
|
+
const links = (entry.links || []).map(linkLabel);
|
|
52
|
+
return `${entry.name || entry.skill_id} ${entry.skill_id} ${entry.kind} ${state} links: ${links.length ? links.join(', ') : 'none'}`;
|
|
53
|
+
});
|
|
54
|
+
if (page.has_more && page.next) lines.push(`More Skills: rerun with --after ${page.next}`);
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** What a link change did and where the resource is listed now. */
|
|
59
|
+
export function linkChangeSummary(result) {
|
|
60
|
+
const parts = [];
|
|
61
|
+
const added = (result?.added || []).map(link => `${link.space_id}/${link.alias}`);
|
|
62
|
+
const removed = (result?.removed || []).map(link => `${link.space_id}/${link.alias}`);
|
|
63
|
+
if (added.length) parts.push(`added: ${added.join(', ')}`);
|
|
64
|
+
if (removed.length) parts.push(`removed: ${removed.join(', ')}`);
|
|
65
|
+
if (result?.became_standalone) parts.push('now your standalone Skill');
|
|
66
|
+
if (result?.replayed) parts.push('confirmed retry');
|
|
67
|
+
const current = (result?.links || []).map(linkLabel);
|
|
68
|
+
return `${parts.join('; ') || 'No change'}. Listed in: ${current.length ? current.join(', ') : 'none'}.`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function linkEntries(value, option, required, optional = []) {
|
|
72
|
+
const parsed = parseJson(value, option);
|
|
73
|
+
const valid = entry => entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
74
|
+
&& required.every(key => key in entry) && Object.keys(entry).every(key => required.includes(key) || optional.includes(key));
|
|
75
|
+
if (!Array.isArray(parsed) || !parsed.length || !parsed.every(valid)) {
|
|
76
|
+
throw usageError(`${option} must be a JSON array of {${[...required, ...optional.map(key => `${key}?`)].join(', ')}} entries.`);
|
|
77
|
+
}
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
5
80
|
|
|
6
81
|
async function loadSkillSyncEngine() {
|
|
7
82
|
return import('../../dist/skill-sync/index.js');
|
|
8
83
|
}
|
|
9
84
|
|
|
85
|
+
function cliDefaultOwnsAgentFolders(runtime) {
|
|
86
|
+
if (runtime.profileSource !== 'explicit' || !runtime.profileName) return true;
|
|
87
|
+
try {
|
|
88
|
+
return runtime.profileName === getCurrentProfileName(loadConfig());
|
|
89
|
+
} catch {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
10
94
|
export async function syncSkillsHandler(ctx, {
|
|
11
95
|
refresh = ensureFreshOAuthCredential, loadEngine = loadSkillSyncEngine,
|
|
12
96
|
reconcile = reconcileAllSkills, install = installSkillSyncService,
|
|
@@ -20,6 +104,9 @@ export async function syncSkillsHandler(ctx, {
|
|
|
20
104
|
jwt: ctx.runtime.jwt,
|
|
21
105
|
userId: settings.user_id || userId,
|
|
22
106
|
honorSyncEnabled: Boolean(ctx.options.electronRepeat),
|
|
107
|
+
// Only the CLI default's skills go into the agent folders; another profile
|
|
108
|
+
// syncs its own folder.
|
|
109
|
+
linkAgentFolders: cliDefaultOwnsAgentFolders(ctx.runtime),
|
|
23
110
|
runAccountSync: (serverUrl, jwt, dependencies, options) => runSkillSync(
|
|
24
111
|
serverUrl, jwt, { ...dependencies, fetchSyncSettings: async () => settings }, options,
|
|
25
112
|
),
|
|
@@ -48,6 +135,160 @@ export async function syncSkillsHandler(ctx, {
|
|
|
48
135
|
}
|
|
49
136
|
|
|
50
137
|
export const skillsCommandSpecs = [
|
|
138
|
+
{
|
|
139
|
+
command_path: ['skills', 'list'],
|
|
140
|
+
summary: 'List every Skill you can see, with its kind, editability and Space links.',
|
|
141
|
+
when_to_use: 'Find a Skill and its exact edit target before reading or editing it, or see what a Space and its sub-Spaces list. Links to Spaces you cannot open are omitted; an unavailable link gives no access.',
|
|
142
|
+
args_schema: { arguments: [], options: [
|
|
143
|
+
{ flags: '--space <id>', description: 'Only Skills listed by this Space and, by default, its sub-Spaces.' },
|
|
144
|
+
{ flags: '--no-child-spaces', description: 'With --space: direct links of that Space only.' },
|
|
145
|
+
{ flags: '--include-disabled', description: 'Include Skills you disabled.' },
|
|
146
|
+
{ flags: '--after <cursor>', description: 'Continue from the next cursor of the previous page.' },
|
|
147
|
+
{ flags: '--limit <n>', description: 'Page size between 1 and 100 (default 50).' },
|
|
148
|
+
] },
|
|
149
|
+
examples: ['notis skills list', 'notis skills list --space <space-id> --no-child-spaces --json'],
|
|
150
|
+
mutates: false, idempotent: true, require_auth: true,
|
|
151
|
+
backend_call: { type: 'http', path: '/portal_skills/inventory' },
|
|
152
|
+
async handler(ctx) {
|
|
153
|
+
const body = {};
|
|
154
|
+
if (ctx.options.space !== undefined) body.space_id = ctx.options.space;
|
|
155
|
+
if (ctx.options.childSpaces === false) {
|
|
156
|
+
if (body.space_id === undefined) throw usageError('--no-child-spaces needs --space <id>.');
|
|
157
|
+
body.include_child_spaces = false;
|
|
158
|
+
}
|
|
159
|
+
if (ctx.options.includeDisabled) body.include_disabled = true;
|
|
160
|
+
if (ctx.options.after !== undefined) body.after = ctx.options.after;
|
|
161
|
+
if (ctx.options.limit !== undefined) {
|
|
162
|
+
const limit = Number(ctx.options.limit);
|
|
163
|
+
if (!/^\d+$/.test(String(ctx.options.limit)) || limit < 1 || limit > 100) throw usageError('Pass --limit between 1 and 100.');
|
|
164
|
+
body.limit = limit;
|
|
165
|
+
}
|
|
166
|
+
const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path: '/portal_skills/inventory', body });
|
|
167
|
+
return ctx.output.emitSuccess({ command: 'skills list', data: result.payload,
|
|
168
|
+
humanSummary: skillInventoryLines(result.payload), meta: { mutating: false, request_id: result.requestId } });
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
command_path: ['skills', 'create'],
|
|
173
|
+
summary: 'Create a native Skill, standalone or linked to an editable Space.',
|
|
174
|
+
when_to_use: 'Create complete instructions or files without provider installation. Choose an optional Space destination, dry-run, and keep the exact request key on retry.',
|
|
175
|
+
args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON containing name, optional description, and skill_md or base64 files.' }],
|
|
176
|
+
options: [{ flags: '--request-id <key>', description: 'Stable creation intent key; reuse after an uncertain reply.' },
|
|
177
|
+
{ flags: '--dry-run', description: 'Validate without creating an identity or uploading files.' }] },
|
|
178
|
+
examples: ['notis skills create ./skill.json --request-id create-1 --dry-run'],
|
|
179
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
180
|
+
backend_call: { type: 'http', path: '/portal_skills/native-create' },
|
|
181
|
+
handler(ctx) {
|
|
182
|
+
const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill creation file');
|
|
183
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
184
|
+
|| Object.keys(payload).some(key => !['destination', 'name', 'description', 'skill_md', 'files'].includes(key))) {
|
|
185
|
+
throw usageError('Provide a name, complete instructions or files, and an optional current destination; never override ownership.');
|
|
186
|
+
}
|
|
187
|
+
const destination = payload.destination ?? { kind: 'standalone' };
|
|
188
|
+
if (!(exactKeys(destination, ['kind']) && destination.kind === 'standalone')
|
|
189
|
+
&& !exactKeys(destination, ['space_id', 'alias', 'revision'])) throw usageError('Choose a standalone destination or exact Space, alias and revision.');
|
|
190
|
+
const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
|
|
191
|
+
if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
|
|
192
|
+
return nativeSkillAuthoring(ctx, { ...payload, operation: 'create', destination,
|
|
193
|
+
request_id: request, dry_run: Boolean(ctx.options.dryRun) }, '/portal_skills/native-create');
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
command_path: ['skills', 'settings', 'read'],
|
|
198
|
+
summary: 'Read a native Skill personal enablement, agent targets and settings revision.',
|
|
199
|
+
when_to_use: 'Inspect direct-owner preferences without changing shared instructions or Space access.',
|
|
200
|
+
args_schema: { arguments: [{ token: '<skill-id>', key: 'skillId', description: 'Exact native Skill ID.' }], options: [] },
|
|
201
|
+
mutates: false, idempotent: true, require_auth: true,
|
|
202
|
+
backend_call: { type: 'http', path: '/portal_skills/native-settings' },
|
|
203
|
+
handler(ctx) { return nativeSkillAuthoring(ctx, { operation: 'read', target: { skill_id: ctx.args.skillId } }, '/portal_skills/native-settings'); },
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
command_path: ['skills', 'settings', 'update'],
|
|
207
|
+
summary: 'Update personal native Skill settings with a current revision and stable retry key.',
|
|
208
|
+
when_to_use: 'Enable or disable your agents without changing a shared Skill definition or another Space.',
|
|
209
|
+
args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON with target, revision and patch (enabled or agent_targets).' }],
|
|
210
|
+
options: [{ flags: '--request-id <key>', description: 'Stable settings intent; reuse after a lost reply.' },
|
|
211
|
+
{ flags: '--dry-run', description: 'Validate current ownership and revision without changing settings.' }] },
|
|
212
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
213
|
+
backend_call: { type: 'http', path: '/portal_skills/native-settings' },
|
|
214
|
+
handler(ctx) {
|
|
215
|
+
const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill settings file');
|
|
216
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
217
|
+
|| Object.keys(payload).sort().join(',') !== 'patch,revision,target'
|
|
218
|
+
|| !payload.target || Object.keys(payload.target).sort().join(',') !== 'access_revision,skill_id') {
|
|
219
|
+
throw usageError('Use a current standalone target, settings revision and personal settings patch.');
|
|
220
|
+
}
|
|
221
|
+
const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
|
|
222
|
+
if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
|
|
223
|
+
return nativeSkillAuthoring(ctx, { ...payload, operation: 'update', request_id: request, dry_run: Boolean(ctx.options.dryRun) }, '/portal_skills/native-settings');
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
command_path: ['skills', 'read'],
|
|
228
|
+
summary: 'Inspect a native Skill through direct ownership or an editable Space link.',
|
|
229
|
+
when_to_use: 'Read the exact target and content version before editing. A Space target uses its current Editor authority, never a personal fallback.',
|
|
230
|
+
args_schema: { arguments: [{ token: '[skill-id]', key: 'skillId', description: 'Exact directly owned Skill ID; omit when passing --target.' }],
|
|
231
|
+
options: [{ flags: '--files', description: 'Include every verified supporting file as base64.' },
|
|
232
|
+
{ flags: '--target <json>', description: 'Exact {skill_id} or {space_id,binding_id} read target; do not combine with a Skill ID.' }] },
|
|
233
|
+
examples: ['notis skills read <skill-id> --files', 'notis skills read --target \'{"space_id":"<id>","binding_id":"<binding>"}\' --files'],
|
|
234
|
+
mutates: false, idempotent: true, require_auth: true,
|
|
235
|
+
backend_call: { type: 'http', path: '/portal_skills/native-authoring' },
|
|
236
|
+
handler(ctx) {
|
|
237
|
+
if (Boolean(ctx.args.skillId) === Boolean(ctx.options.target)) throw usageError('Pass either a Skill ID or --target <json>.');
|
|
238
|
+
const target = skillTarget(ctx.options.target ? parseJson(ctx.options.target, '--target') : { skill_id: ctx.args.skillId }, false);
|
|
239
|
+
return nativeSkillAuthoring(ctx, { operation: 'read', target,
|
|
240
|
+
include_files: Boolean(ctx.options.files) });
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
command_path: ['skills', 'update'],
|
|
245
|
+
summary: 'Edit a native Skill through its direct or Space target, preserving its ID and supporting files.',
|
|
246
|
+
when_to_use: 'Use the target/version from skills list or skills read, dry-run, then reuse the same request key and bytes to save or recover a lost reply. The same edit file works for a standalone or a Space target; an optional links key changes where the Skill is listed in the same transaction.',
|
|
247
|
+
args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON with direct or Space target, version, skill_md or base64 files, optional name/description and links {add,remove}.' }],
|
|
248
|
+
options: [{ flags: '--request-id <key>', description: 'Stable edit intent key; reuse after lost replies.' },
|
|
249
|
+
{ flags: '--dry-run', description: 'Check current access, content and revisions without uploading or saving.' }] },
|
|
250
|
+
examples: ['notis skills update ./skill-edit.json --request-id edit-1 --dry-run'],
|
|
251
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
252
|
+
backend_call: { type: 'http', path: '/portal_skills/native-authoring' },
|
|
253
|
+
handler(ctx) {
|
|
254
|
+
const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill edit file');
|
|
255
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
256
|
+
|| Object.keys(payload).some(key => !['target', 'version', 'skill_md', 'files', 'name', 'description', 'links'].includes(key))) {
|
|
257
|
+
throw usageError('Use an exact target, version, skill_md or files, and optional links; never override ownership.');
|
|
258
|
+
}
|
|
259
|
+
skillTarget(payload.target, true);
|
|
260
|
+
if ('links' in payload && (!payload.links || typeof payload.links !== 'object' || Array.isArray(payload.links)
|
|
261
|
+
|| !Object.keys(payload.links).length || Object.keys(payload.links).some(key => !['add', 'remove'].includes(key)))) {
|
|
262
|
+
throw usageError('links must be an object with add and/or remove arrays.');
|
|
263
|
+
}
|
|
264
|
+
return nativeSkillAuthoring(ctx, { ...payload, operation: 'update', request_id: requestKey(ctx),
|
|
265
|
+
dry_run: Boolean(ctx.options.dryRun) });
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
command_path: ['skills', 'links'],
|
|
270
|
+
summary: 'Add, remove or move a Skill between Spaces; the Skill keeps its ID.',
|
|
271
|
+
when_to_use: 'Change where a Skill is listed without editing its content. A move is one add plus one remove in one transaction. You must be an Editor of every Space you touch and able to edit the Skill; removing its last available link keeps it as your own standalone Skill.',
|
|
272
|
+
args_schema: { arguments: [{ token: '<skill-id>', key: 'skillId', description: 'Exact Skill ID from skills list.' }], options: [
|
|
273
|
+
{ flags: '--add <json>', description: 'JSON array of {space_id, space_revision, alias?} links to add; alias defaults to the Skill name.' },
|
|
274
|
+
{ flags: '--remove <json>', description: 'JSON array of {binding_id, binding_revision} links to remove, from skills list.' },
|
|
275
|
+
{ flags: '--request-id <key>', description: 'Stable intent key; reuse it unchanged when retrying this change.' },
|
|
276
|
+
] },
|
|
277
|
+
examples: ['notis skills links <skill-id> --add \'[{"space_id":"<space-id>","space_revision":3}]\' --request-id link-1',
|
|
278
|
+
'notis skills links <skill-id> --add \'[{"space_id":"<to-space-id>","space_revision":3}]\' --remove \'[{"binding_id":"<binding-id>","binding_revision":2}]\' --request-id move-1'],
|
|
279
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
280
|
+
backend_call: { type: 'http', path: '/portal_spaces/resource-links' },
|
|
281
|
+
async handler(ctx) {
|
|
282
|
+
const links = {};
|
|
283
|
+
if (ctx.options.add !== undefined) links.add = linkEntries(ctx.options.add, '--add', ['space_id', 'space_revision'], ['alias']);
|
|
284
|
+
if (ctx.options.remove !== undefined) links.remove = linkEntries(ctx.options.remove, '--remove', ['binding_id', 'binding_revision']);
|
|
285
|
+
if (!Object.keys(links).length) throw usageError('Pass --add and/or --remove.');
|
|
286
|
+
const body = { kind: 'skill', resource_id: ctx.args.skillId, links, request_id: requestKey(ctx) };
|
|
287
|
+
const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path: '/portal_spaces/resource-links', body, spacesProtocol: 1 });
|
|
288
|
+
return ctx.output.emitSuccess({ command: 'skills links', data: result.payload,
|
|
289
|
+
humanSummary: linkChangeSummary(result.payload), meta: { mutating: true, request_id: result.requestId } });
|
|
290
|
+
},
|
|
291
|
+
},
|
|
51
292
|
{
|
|
52
293
|
command_path: ['skills', 'sync'],
|
|
53
294
|
summary: 'Synchronize account skills and keep the three Notis base skills current.',
|