@notis_ai/cli 0.2.7 → 0.2.8
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 +15 -0
- package/dist/scaffolds/notis-database/app/page.tsx +38 -41
- package/dist/scaffolds/notis-database/lib/types.ts +5 -7
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +1839 -0
- package/dist/scaffolds/notis-database/notis.config.ts +48 -1
- package/dist/scaffolds/notis-database/package-lock.json +3935 -0
- package/dist/scaffolds/notis-database/package.json +2 -1
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/package.json +4 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -2
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useMultiSelect.ts +38 -2
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/index.ts +30 -1
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/runtime.ts +76 -17
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +4 -1
- package/dist/scaffolds/notis-journal/CHANGELOG.md +4 -0
- package/dist/scaffolds/notis-journal/app/page.tsx +1 -0
- package/dist/scaffolds/notis-journal/package-lock.json +4615 -0
- package/dist/scaffolds/notis-journal/package.json +2 -2
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +52 -1
- package/dist/scaffolds/notis-notes/app/page.tsx +39 -46
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +596 -0
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +144 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +752 -0
- package/dist/scaffolds/notis-notes/notis.config.ts +48 -3
- package/dist/scaffolds/notis-notes/package-lock.json +4636 -0
- package/dist/scaffolds/notis-notes/package.json +7 -3
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-random/CHANGELOG.md +12 -2
- package/dist/scaffolds/notis-random/app/history/page.tsx +5 -4
- package/dist/scaffolds/notis-random/app/page.tsx +141 -74
- package/dist/scaffolds/notis-random/components/ui/button.tsx +50 -0
- package/dist/scaffolds/notis-random/components/ui/card.tsx +16 -0
- package/dist/scaffolds/notis-random/components/ui/input.tsx +23 -0
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +26 -7
- package/dist/scaffolds/notis-random/lib/rng.ts +179 -19
- package/dist/scaffolds/notis-random/lib/roll-record.ts +103 -16
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +753 -0
- package/dist/scaffolds/notis-random/notis.config.ts +47 -3
- package/dist/scaffolds/notis-random/package-lock.json +4513 -0
- package/dist/scaffolds/notis-random/package.json +6 -2
- package/dist/scaffolds/notis-random/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-random/tailwind.config.ts +8 -1
- package/dist/scaffolds.json +8 -17
- package/package.json +2 -1
- package/skills/notis-apps/SKILL.md +449 -144
- package/skills/notis-apps/cli.md +15 -0
- package/skills/notis-cli/SKILL.md +8 -4
- package/skills/notis-onboarding/BRIEF.md +93 -0
- package/src/cli.js +50 -12
- package/src/command-specs/apps.js +154 -21
- package/src/command-specs/index.js +2 -0
- package/src/command-specs/onboarding.js +216 -0
- package/src/command-specs/tools.js +8 -4
- package/src/runtime/app-platform.js +65 -4
- package/src/runtime/desktop-auth.js +66 -17
- package/src/runtime/errors.js +1 -0
- package/src/runtime/output.js +9 -2
- package/src/runtime/profiles.js +179 -17
- package/src/runtime/telemetry.js +92 -0
- package/src/runtime/transport.js +26 -1
- package/template/.harness/index.html.tmpl +30 -2
- package/template/packages/sdk/src/config.ts +38 -1
- package/dist/scaffolds/notis-affiliate-prospects/CHANGELOG.md +0 -7
- package/dist/scaffolds/notis-affiliate-prospects/README.md +0 -20
- package/dist/scaffolds/notis-affiliate-prospects/app/globals.css +0 -42
- package/dist/scaffolds/notis-affiliate-prospects/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-affiliate-prospects/app/page.tsx +0 -248
- package/dist/scaffolds/notis-affiliate-prospects/app/prospects/page.tsx +0 -134
- package/dist/scaffolds/notis-affiliate-prospects/app/segments/page.tsx +0 -108
- package/dist/scaffolds/notis-affiliate-prospects/components/ui/card.tsx +0 -35
- package/dist/scaffolds/notis-affiliate-prospects/index.html +0 -12
- package/dist/scaffolds/notis-affiliate-prospects/lib/affiliate-data.ts +0 -218
- package/dist/scaffolds/notis-affiliate-prospects/lib/demo-prospects.ts +0 -52
- package/dist/scaffolds/notis-affiliate-prospects/lib/notis-tools.ts +0 -100
- package/dist/scaffolds/notis-affiliate-prospects/lib/utils.ts +0 -25
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-fixtures.json +0 -187
- package/dist/scaffolds/notis-affiliate-prospects/notis.config.ts +0 -65
- package/dist/scaffolds/notis-affiliate-prospects/package.json +0 -32
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/config.ts +0 -90
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useDatabase.ts +0 -76
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useUpsertDocument.ts +0 -50
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/styles.css +0 -38
- package/dist/scaffolds/notis-affiliate-prospects/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-affiliate-prospects/src/dev-main.tsx +0 -72
- package/dist/scaffolds/notis-affiliate-prospects/src/mock-runtime.ts +0 -242
- package/dist/scaffolds/notis-affiliate-prospects/tailwind.config.ts +0 -51
- package/dist/scaffolds/notis-affiliate-prospects/tsconfig.json +0 -23
- package/dist/scaffolds/notis-affiliate-prospects/vite.config.ts +0 -11
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useBackend.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotis.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotisNavigation.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTool.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTools.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTopBarSearch.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/provider.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/ui.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/vite.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/tsconfig.json +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-random}/components.json +0 -0
package/src/runtime/profiles.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { dirname, join, parse, resolve } from 'node:path';
|
|
4
4
|
import { CliError, EXIT_CODES } from './errors.js';
|
|
5
5
|
import { getDesktopAuthRecovery } from './desktop-auth.js';
|
|
6
6
|
|
|
@@ -9,6 +9,8 @@ export const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
|
9
9
|
export const WORKSPACE_DIR = join(CONFIG_DIR, 'workspace');
|
|
10
10
|
export const DEFAULT_API_BASE = 'https://api.notis.ai';
|
|
11
11
|
export const DEFAULT_PROFILE = 'default';
|
|
12
|
+
const WORKTREE_RUNTIME_FILENAME = join('.context', 'notis-runtime.json');
|
|
13
|
+
const WORKTREE_ROUTING_FILENAME = join('.context', 'notis-routing.json');
|
|
12
14
|
const LOCAL_DEFAULT_API_BASES = new Set([
|
|
13
15
|
'http://localhost:3001',
|
|
14
16
|
'http://127.0.0.1:3001',
|
|
@@ -77,17 +79,129 @@ export function normalizeConfig(rawConfig = {}) {
|
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
function readJsonFile(path) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function findUp(filename, startDir = process.cwd()) {
|
|
91
|
+
let current = resolve(startDir);
|
|
92
|
+
const root = parse(current).root;
|
|
93
|
+
while (true) {
|
|
94
|
+
const candidate = join(current, filename);
|
|
95
|
+
if (existsSync(candidate)) {
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
if (current === root) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
current = dirname(current);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function processIsAlive(pid) {
|
|
106
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
process.kill(pid, 0);
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resolveWorktreeRuntime(startDir = process.cwd()) {
|
|
118
|
+
if (
|
|
119
|
+
process.env.NODE_ENV === 'test' &&
|
|
120
|
+
process.env.NOTIS_TEST_DISABLE_WORKTREE_ROUTING === '1'
|
|
121
|
+
) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const runtimePath = findUp(WORKTREE_RUNTIME_FILENAME, startDir);
|
|
125
|
+
const routingPath = findUp(WORKTREE_ROUTING_FILENAME, startDir);
|
|
126
|
+
const routing = routingPath ? readJsonFile(routingPath) : null;
|
|
127
|
+
|
|
128
|
+
if (!runtimePath) {
|
|
129
|
+
if (routing?.mode === 'local-only') {
|
|
130
|
+
throw new CliError({
|
|
131
|
+
code: 'dev_runtime_unavailable',
|
|
132
|
+
message: `This worktree is local-only, but its dev.sh runtime is not active`,
|
|
133
|
+
exitCode: EXIT_CODES.network,
|
|
134
|
+
hints: [
|
|
135
|
+
{ message: 'Start ./dev.sh in this worktree, then retry the command.' },
|
|
136
|
+
{ message: `Routing policy: ${routingPath}` },
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const runtime = readJsonFile(runtimePath);
|
|
144
|
+
const apiBase = typeof runtime?.api_base === 'string' ? runtime.api_base.replace(/\/+$/, '') : '';
|
|
145
|
+
const configFile = typeof runtime?.config_file === 'string' ? runtime.config_file : '';
|
|
146
|
+
const pid = Number(runtime?.dev_pid);
|
|
147
|
+
if (
|
|
148
|
+
runtime?.mode !== 'local-only' ||
|
|
149
|
+
!isLocalApiBase(apiBase) ||
|
|
150
|
+
!configFile ||
|
|
151
|
+
!processIsAlive(pid)
|
|
152
|
+
) {
|
|
153
|
+
throw new CliError({
|
|
154
|
+
code: 'dev_runtime_unavailable',
|
|
155
|
+
message: 'The local-only worktree runtime is stale or invalid',
|
|
156
|
+
exitCode: EXIT_CODES.network,
|
|
157
|
+
hints: [
|
|
158
|
+
{ message: 'Restart ./dev.sh in this worktree, then retry the command.' },
|
|
159
|
+
{ message: `Runtime lease: ${runtimePath}` },
|
|
160
|
+
],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
...runtime,
|
|
166
|
+
api_base: apiBase,
|
|
167
|
+
config_file: resolve(dirname(runtimePath), configFile),
|
|
168
|
+
runtime_path: runtimePath,
|
|
169
|
+
routing_path: routingPath,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function resolveConfigFile(runtime = null) {
|
|
174
|
+
if (runtime?.config_file) {
|
|
175
|
+
return runtime.config_file;
|
|
176
|
+
}
|
|
177
|
+
const envConfigFile = process.env.NOTIS_CLI_CONFIG_FILE;
|
|
178
|
+
if (envConfigFile) {
|
|
179
|
+
return resolve(envConfigFile);
|
|
180
|
+
}
|
|
181
|
+
return CONFIG_FILE;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function loadConfig(runtime = null) {
|
|
185
|
+
const configFile = resolveConfigFile(runtime);
|
|
186
|
+
if (!existsSync(configFile)) {
|
|
82
187
|
return normalizeConfig({});
|
|
83
188
|
}
|
|
84
189
|
|
|
85
|
-
|
|
190
|
+
// The desktop app rewrites this file while the CLI may be reading it, so a
|
|
191
|
+
// torn or corrupt read is expected rather than exceptional. Degrade to
|
|
192
|
+
// "unauthenticated", which surfaces the actionable auth_missing error instead
|
|
193
|
+
// of a raw SyntaxError from deep inside the runtime.
|
|
194
|
+
try {
|
|
195
|
+
return normalizeConfig(JSON.parse(readFileSync(configFile, 'utf-8')));
|
|
196
|
+
} catch {
|
|
197
|
+
return normalizeConfig({});
|
|
198
|
+
}
|
|
86
199
|
}
|
|
87
200
|
|
|
88
|
-
export function saveConfig(config) {
|
|
89
|
-
|
|
90
|
-
|
|
201
|
+
export function saveConfig(config, runtime = null) {
|
|
202
|
+
const configFile = resolveConfigFile(runtime);
|
|
203
|
+
mkdirSync(dirname(configFile), { recursive: true });
|
|
204
|
+
writeFileSync(configFile, JSON.stringify(normalizeConfig(config), null, 2), { mode: 0o600 });
|
|
91
205
|
}
|
|
92
206
|
|
|
93
207
|
export function getProfile(config, profileName) {
|
|
@@ -225,10 +339,26 @@ export function resolveRuntimeProfile(
|
|
|
225
339
|
globalOptions = {},
|
|
226
340
|
{ requireAuth = true, includeDebugEntitlementOverride = true } = {},
|
|
227
341
|
) {
|
|
228
|
-
const
|
|
342
|
+
const worktreeRuntime = resolveWorktreeRuntime();
|
|
343
|
+
const config = loadConfig(worktreeRuntime);
|
|
229
344
|
const profileName = getCurrentProfileName(config, globalOptions.profile);
|
|
230
|
-
const
|
|
231
|
-
|
|
345
|
+
const requestedApiBase = globalOptions.apiBase;
|
|
346
|
+
if (
|
|
347
|
+
worktreeRuntime &&
|
|
348
|
+
requestedApiBase &&
|
|
349
|
+
requestedApiBase.replace(/\/+$/, '') !== worktreeRuntime.api_base
|
|
350
|
+
) {
|
|
351
|
+
throw new CliError({
|
|
352
|
+
code: 'dev_runtime_route_mismatch',
|
|
353
|
+
message: `This local-only worktree cannot route to ${requestedApiBase}`,
|
|
354
|
+
exitCode: EXIT_CODES.usage,
|
|
355
|
+
hints: [{ message: `Expected local API: ${worktreeRuntime.api_base}` }],
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
const apiBase = worktreeRuntime
|
|
359
|
+
? worktreeRuntime.api_base
|
|
360
|
+
: getApiBase(config, profileName, globalOptions.apiBase);
|
|
361
|
+
const jwt = worktreeRuntime ? getProfile(config, profileName).jwt : getJwt(config, profileName);
|
|
232
362
|
const profile = getProfile(config, profileName);
|
|
233
363
|
const agentMode = isAgentMode(globalOptions);
|
|
234
364
|
const nonInteractive = isNonInteractive(globalOptions);
|
|
@@ -239,11 +369,14 @@ export function resolveRuntimeProfile(
|
|
|
239
369
|
: null;
|
|
240
370
|
|
|
241
371
|
if (requireAuth && !jwt) {
|
|
242
|
-
const recovery = getDesktopAuthRecovery(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
372
|
+
const recovery = getDesktopAuthRecovery(
|
|
373
|
+
{
|
|
374
|
+
apiBase,
|
|
375
|
+
desktopAppName: profile.desktop_app_name,
|
|
376
|
+
desktopPid: profile.desktop_pid,
|
|
377
|
+
},
|
|
378
|
+
{ mode: 'missing' },
|
|
379
|
+
);
|
|
247
380
|
throw new CliError({
|
|
248
381
|
code: 'auth_missing',
|
|
249
382
|
message: `No JWT configured for profile ${profileName}`,
|
|
@@ -251,16 +384,30 @@ export function resolveRuntimeProfile(
|
|
|
251
384
|
hints: recovery.hints,
|
|
252
385
|
});
|
|
253
386
|
}
|
|
387
|
+
if (
|
|
388
|
+
worktreeRuntime?.expected_user_id &&
|
|
389
|
+
getJwtSubject(jwt) !== worktreeRuntime.expected_user_id
|
|
390
|
+
) {
|
|
391
|
+
throw new CliError({
|
|
392
|
+
code: 'dev_runtime_identity_mismatch',
|
|
393
|
+
message: 'The scoped dev credential does not belong to this worktree test user',
|
|
394
|
+
exitCode: EXIT_CODES.auth,
|
|
395
|
+
hints: [
|
|
396
|
+
{ message: 'Restart ./dev.sh to restore the approved worktree identity.' },
|
|
397
|
+
{ message: `Expected user: ${worktreeRuntime.expected_user_id}` },
|
|
398
|
+
],
|
|
399
|
+
});
|
|
400
|
+
}
|
|
254
401
|
|
|
255
402
|
// An explicit NOTIS_JWT is a complete credential override. Use it verbatim
|
|
256
403
|
// and never replace it with a token later synced by the desktop profile.
|
|
257
|
-
const usingEnvJwt = Boolean(process.env.NOTIS_JWT);
|
|
404
|
+
const usingEnvJwt = !worktreeRuntime && Boolean(process.env.NOTIS_JWT);
|
|
258
405
|
return {
|
|
259
406
|
config,
|
|
260
407
|
profileName,
|
|
261
408
|
apiBase,
|
|
262
409
|
jwt,
|
|
263
|
-
credentialSource: usingEnvJwt ? 'env' : 'profile',
|
|
410
|
+
credentialSource: worktreeRuntime ? 'worktree' : usingEnvJwt ? 'env' : 'profile',
|
|
264
411
|
desktopAppName: usingEnvJwt ? undefined : profile.desktop_app_name,
|
|
265
412
|
desktopPid: usingEnvJwt ? undefined : profile.desktop_pid,
|
|
266
413
|
agentMode,
|
|
@@ -268,6 +415,7 @@ export function resolveRuntimeProfile(
|
|
|
268
415
|
outputMode,
|
|
269
416
|
timeoutMs,
|
|
270
417
|
debugEntitlementOverride,
|
|
418
|
+
worktreeRuntime,
|
|
271
419
|
};
|
|
272
420
|
}
|
|
273
421
|
|
|
@@ -285,6 +433,20 @@ export function getJwtExpiration(jwt) {
|
|
|
285
433
|
}
|
|
286
434
|
}
|
|
287
435
|
|
|
436
|
+
export function getJwtSubject(jwt) {
|
|
437
|
+
if (typeof jwt !== 'string' || !jwt) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const parts = jwt.split('.');
|
|
442
|
+
if (parts.length !== 3) return null;
|
|
443
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
|
|
444
|
+
return typeof payload.sub === 'string' && payload.sub ? payload.sub : null;
|
|
445
|
+
} catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
288
450
|
export function isJwtExpired(jwt, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
289
451
|
const expiration = getJwtExpiration(jwt);
|
|
290
452
|
return expiration !== null && expiration <= nowSeconds;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const ERROR_CATEGORIES = new Map([
|
|
2
|
+
[2, 'usage'],
|
|
3
|
+
[3, 'auth'],
|
|
4
|
+
[4, 'network'],
|
|
5
|
+
[5, 'conflict'],
|
|
6
|
+
[6, 'backend'],
|
|
7
|
+
[7, 'unexpected'],
|
|
8
|
+
[8, 'payment'],
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export function cliDurationBucket(durationMs) {
|
|
12
|
+
if (durationMs < 1_000) return 'under_1s';
|
|
13
|
+
if (durationMs < 5_000) return '1s_to_5s';
|
|
14
|
+
if (durationMs < 30_000) return '5s_to_30s';
|
|
15
|
+
return 'over_30s';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function cliBackendKind(spec) {
|
|
19
|
+
const value = String(spec?.backend_call?.type || '').toLowerCase();
|
|
20
|
+
if (value === 'tool') return 'tool';
|
|
21
|
+
if (value === 'http') return 'http';
|
|
22
|
+
if (value === 'local') return 'local';
|
|
23
|
+
if (value.includes('health')) return 'health';
|
|
24
|
+
if (value.includes('local')) return 'local';
|
|
25
|
+
return 'other';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildCliTelemetryPayload({
|
|
29
|
+
spec,
|
|
30
|
+
runtime,
|
|
31
|
+
result,
|
|
32
|
+
durationMs,
|
|
33
|
+
error,
|
|
34
|
+
}) {
|
|
35
|
+
return {
|
|
36
|
+
command_id: spec.command_path.join('.'),
|
|
37
|
+
backend_kind: cliBackendKind(spec),
|
|
38
|
+
result,
|
|
39
|
+
error_category: result === 'failed'
|
|
40
|
+
? (ERROR_CATEGORIES.get(Number(error?.exitCode)) || 'unexpected')
|
|
41
|
+
: 'none',
|
|
42
|
+
duration_bucket: cliDurationBucket(durationMs),
|
|
43
|
+
cli_version: runtime.cliVersion,
|
|
44
|
+
agent_mode: Boolean(runtime.agentMode),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function reportCliCommand({
|
|
49
|
+
spec,
|
|
50
|
+
runtime,
|
|
51
|
+
result,
|
|
52
|
+
durationMs,
|
|
53
|
+
error = null,
|
|
54
|
+
fetchImpl = globalThis.fetch,
|
|
55
|
+
}) {
|
|
56
|
+
// Backend tool calls emit authoritative surface/tool telemetry server-side.
|
|
57
|
+
// This client event exists only for local commands that otherwise leave no
|
|
58
|
+
// observable runtime boundary.
|
|
59
|
+
if (
|
|
60
|
+
cliBackendKind(spec) !== 'local'
|
|
61
|
+
|| !runtime?.jwt
|
|
62
|
+
|| !runtime?.apiBase
|
|
63
|
+
|| typeof fetchImpl !== 'function'
|
|
64
|
+
) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const timeout = setTimeout(() => controller.abort(), 1_500);
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetchImpl(`${runtime.apiBase}/cli_telemetry`, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
Authorization: `Bearer ${runtime.jwt}`,
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'X-Notis-CLI-Version': runtime.cliVersion,
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify(buildCliTelemetryPayload({
|
|
78
|
+
spec,
|
|
79
|
+
runtime,
|
|
80
|
+
result,
|
|
81
|
+
durationMs,
|
|
82
|
+
error,
|
|
83
|
+
})),
|
|
84
|
+
signal: controller.signal,
|
|
85
|
+
});
|
|
86
|
+
return response.ok;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
} finally {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/runtime/transport.js
CHANGED
|
@@ -4,6 +4,7 @@ import { CliError, EXIT_CODES } from './errors.js';
|
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_PROFILE,
|
|
6
6
|
getProfile,
|
|
7
|
+
getJwtSubject,
|
|
7
8
|
isJwtExpired,
|
|
8
9
|
loadConfig,
|
|
9
10
|
} from './profiles.js';
|
|
@@ -122,6 +123,18 @@ function normalizeBackendError(status, payload, runtime) {
|
|
|
122
123
|
});
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
if (status === 402) {
|
|
127
|
+
// Distinct from auth on purpose: an agent that sees an auth exit code will
|
|
128
|
+
// loop trying to re-authenticate, when the actual fix is to add credit.
|
|
129
|
+
return new CliError({
|
|
130
|
+
code: 'payment_required',
|
|
131
|
+
message,
|
|
132
|
+
exitCode: EXIT_CODES.payment,
|
|
133
|
+
details: payload || {},
|
|
134
|
+
hints: payload?.hints || [],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
125
138
|
if (status >= 400 && status < 500) {
|
|
126
139
|
return new CliError({
|
|
127
140
|
code: 'usage_error',
|
|
@@ -149,7 +162,7 @@ function reloadJwtFromConfig(runtime) {
|
|
|
149
162
|
const profileName = runtime.profileName || DEFAULT_PROFILE;
|
|
150
163
|
let profile;
|
|
151
164
|
try {
|
|
152
|
-
profile = getProfile(loadConfig(), profileName);
|
|
165
|
+
profile = getProfile(loadConfig(runtime.worktreeRuntime), profileName);
|
|
153
166
|
} catch {
|
|
154
167
|
return false;
|
|
155
168
|
}
|
|
@@ -157,6 +170,18 @@ function reloadJwtFromConfig(runtime) {
|
|
|
157
170
|
if (!nextJwt || nextJwt === runtime.jwt) {
|
|
158
171
|
return false;
|
|
159
172
|
}
|
|
173
|
+
const expectedUserId = runtime.worktreeRuntime?.expected_user_id;
|
|
174
|
+
if (expectedUserId && getJwtSubject(nextJwt) !== expectedUserId) {
|
|
175
|
+
throw new CliError({
|
|
176
|
+
code: 'dev_runtime_identity_mismatch',
|
|
177
|
+
message: 'The refreshed scoped dev credential does not belong to this worktree test user',
|
|
178
|
+
exitCode: EXIT_CODES.auth,
|
|
179
|
+
hints: [
|
|
180
|
+
{ message: 'Restart ./dev.sh to restore the approved worktree identity.' },
|
|
181
|
+
{ message: `Expected user: ${expectedUserId}` },
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
}
|
|
160
185
|
runtime.jwt = nextJwt;
|
|
161
186
|
runtime.desktopAppName = profile.desktop_app_name;
|
|
162
187
|
runtime.desktopPid = profile.desktop_pid;
|
|
@@ -107,6 +107,33 @@
|
|
|
107
107
|
const apiBase = {{API_BASE}};
|
|
108
108
|
const jwt = {{JWT}};
|
|
109
109
|
const fixtures = {{FIXTURES}};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolves the canned response for one tool call.
|
|
113
|
+
*
|
|
114
|
+
* A plain `"TOOL_NAME"` key answers every call to that tool. An app that
|
|
115
|
+
* reaches several databases through a single tool needs to vary the
|
|
116
|
+
* payload per call, so a key may also be scoped to one argument with
|
|
117
|
+
* `"TOOL_NAME#arg=value"` — for example
|
|
118
|
+
* `"LOCAL_NOTIS_DATABASE_QUERY#database_slug=notes"`. Scoped keys are
|
|
119
|
+
* checked first; the unscoped key stays the fallback.
|
|
120
|
+
*/
|
|
121
|
+
function lookupToolFixture(name, args) {
|
|
122
|
+
const tools = fixtures && fixtures.tools;
|
|
123
|
+
if (!tools) return undefined;
|
|
124
|
+
for (const key of Object.keys(tools)) {
|
|
125
|
+
const sep = key.indexOf('#');
|
|
126
|
+
if (sep === -1 || key.slice(0, sep) !== name) continue;
|
|
127
|
+
const scope = key.slice(sep + 1);
|
|
128
|
+
const eq = scope.indexOf('=');
|
|
129
|
+
if (eq === -1) continue;
|
|
130
|
+
if (String(args[scope.slice(0, eq)]) === scope.slice(eq + 1)) {
|
|
131
|
+
return tools[key];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return Object.prototype.hasOwnProperty.call(tools, name) ? tools[name] : undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
110
137
|
const status = document.getElementById('harness-status');
|
|
111
138
|
const setStatus = (msg, color = '#0f0') => {
|
|
112
139
|
status.textContent = `harness: ${msg}`;
|
|
@@ -197,8 +224,9 @@
|
|
|
197
224
|
},
|
|
198
225
|
callTool: async (name, args) => {
|
|
199
226
|
record('callTool', { name, arguments: args || {} });
|
|
200
|
-
|
|
201
|
-
|
|
227
|
+
const fixture = lookupToolFixture(name, args || {});
|
|
228
|
+
if (fixture !== undefined) {
|
|
229
|
+
return structuredClone(fixture);
|
|
202
230
|
}
|
|
203
231
|
return { ok: true, result: null };
|
|
204
232
|
},
|
|
@@ -62,6 +62,20 @@ export const NOTIS_APP_CATEGORIES = [
|
|
|
62
62
|
|
|
63
63
|
export type NotisAppCategory = typeof NOTIS_APP_CATEGORIES[number];
|
|
64
64
|
|
|
65
|
+
export interface NotisAppDatabaseConfig {
|
|
66
|
+
/** Database slug, as declared by the app. */
|
|
67
|
+
slug: string;
|
|
68
|
+
/**
|
|
69
|
+
* Ship this database's rows to everyone who installs the app.
|
|
70
|
+
*
|
|
71
|
+
* Off by default: declaring a database publishes its STRUCTURE, never its
|
|
72
|
+
* content. Turn it on only for starter content that every installer should
|
|
73
|
+
* receive - a default folder tree, a set of templates - and never for a
|
|
74
|
+
* database that accumulates the author's own data.
|
|
75
|
+
*/
|
|
76
|
+
seedDocuments?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
export interface NotisAppAuthor {
|
|
66
80
|
name: string;
|
|
67
81
|
handle?: string;
|
|
@@ -106,6 +120,19 @@ export const NOTIS_APP_ACCENTS = [
|
|
|
106
120
|
|
|
107
121
|
export type NotisAppAccent = typeof NOTIS_APP_ACCENTS[number];
|
|
108
122
|
|
|
123
|
+
export interface NotisAppCapabilities {
|
|
124
|
+
/**
|
|
125
|
+
* Read every database in the workspace, not only the ones this app declares
|
|
126
|
+
* in `databases` or created itself.
|
|
127
|
+
*
|
|
128
|
+
* An app runtime is otherwise sandboxed to its own databases, so a catalog or
|
|
129
|
+
* explorer app sees an empty list without this. `'read'` is the only accepted
|
|
130
|
+
* value and it never grants writes: `LOCAL_NOTIS_DATABASE_UPSERT_*` stays
|
|
131
|
+
* bound to the app's own databases.
|
|
132
|
+
*/
|
|
133
|
+
workspaceDatabases?: 'read';
|
|
134
|
+
}
|
|
135
|
+
|
|
109
136
|
export interface NotisAppConfig {
|
|
110
137
|
/** URL-safe app slug. Existing apps may still use a display name here. */
|
|
111
138
|
name: string;
|
|
@@ -128,7 +155,17 @@ export interface NotisAppConfig {
|
|
|
128
155
|
/** @deprecated Add release entries to the root CHANGELOG.md instead. */
|
|
129
156
|
versionNotes?: string;
|
|
130
157
|
screenshots?: NotisAppScreenshotConfig[];
|
|
131
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Databases this app owns. A bare string publishes structure only; use the
|
|
160
|
+
* object form to opt a database into shipping its rows to installers.
|
|
161
|
+
*/
|
|
162
|
+
databases?: (string | NotisAppDatabaseConfig)[];
|
|
163
|
+
/**
|
|
164
|
+
* Extra permissions the app asks for at install time. Everything here widens
|
|
165
|
+
* what the app can reach beyond its own data, so each one is surfaced to the
|
|
166
|
+
* user before they install.
|
|
167
|
+
*/
|
|
168
|
+
capabilities?: NotisAppCapabilities;
|
|
132
169
|
routes?: NotisRouteConfig[];
|
|
133
170
|
tools?: string[];
|
|
134
171
|
skills?: NotisAppSkillConfig[];
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## [Affiliate recruiting control center] - 2026-07-23
|
|
4
|
-
|
|
5
|
-
- Added the campaign dashboard, measurable affiliate pipeline, and priority queue.
|
|
6
|
-
- Added responsive prospect and segment views.
|
|
7
|
-
- Added local fixtures for every recruiting segment so the app can be tested before installation.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Affiliate Prospects
|
|
2
|
-
|
|
3
|
-
Local-first Notis app for monitoring the affiliate recruiting pipeline.
|
|
4
|
-
|
|
5
|
-
The app is intentionally a read-only campaign surface. Notis automations and Codex workers own
|
|
6
|
-
discovery, enrichment, outreach preparation, analytics reconciliation, and record updates. This
|
|
7
|
-
keeps one source of truth while avoiding business logic in the UI.
|
|
8
|
-
|
|
9
|
-
## Routes
|
|
10
|
-
|
|
11
|
-
- Dashboard: KPIs, pipeline, priority queue, and segment coverage.
|
|
12
|
-
- Prospects: responsive reference table for every lead and next action.
|
|
13
|
-
- Segments: scorecards linked to the source-controlled segment playbooks.
|
|
14
|
-
|
|
15
|
-
## Data
|
|
16
|
-
|
|
17
|
-
The deployed app references `affiliate_prospects_1` and `affiliate_outreach_events_1`. Their
|
|
18
|
-
canonical schema is documented in `campaign/affiliate-network/data-schema.md`. During local
|
|
19
|
-
development the app uses deterministic mock prospects so it can be reviewed before any workspace
|
|
20
|
-
installation.
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
@tailwind base;
|
|
2
|
-
@tailwind components;
|
|
3
|
-
@tailwind utilities;
|
|
4
|
-
|
|
5
|
-
.affiliate-app-shell {
|
|
6
|
-
container-type: inline-size;
|
|
7
|
-
padding: clamp(1rem, 2.5cqi, 2rem);
|
|
8
|
-
max-width: 1440px;
|
|
9
|
-
margin-inline: auto;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.affiliate-stat-grid {
|
|
13
|
-
display: grid;
|
|
14
|
-
grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.affiliate-dashboard-grid {
|
|
18
|
-
display: grid;
|
|
19
|
-
grid-template-columns: minmax(0, 1fr);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
@container (min-width: 940px) {
|
|
23
|
-
.affiliate-dashboard-grid {
|
|
24
|
-
grid-template-columns: minmax(0, 1.55fr) minmax(300px, 0.75fr);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.affiliate-data-table {
|
|
29
|
-
min-width: 900px;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@media (max-width: 767px) {
|
|
33
|
-
.affiliate-desktop-table {
|
|
34
|
-
display: none;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
@media (min-width: 768px) {
|
|
39
|
-
.affiliate-mobile-cards {
|
|
40
|
-
display: none;
|
|
41
|
-
}
|
|
42
|
-
}
|