atris 3.57.4 → 3.58.6
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 +12 -1
- package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
- package/atris/skills/create-member/SKILL.md +16 -13
- package/atris/skills/design/SKILL.md +27 -4
- package/atris/skills/engines/SKILL.md +55 -12
- package/atris/skills/x-search/SKILL.md +8 -6
- package/atris/skills/youtube/SKILL.md +66 -21
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris/team/executor/MEMBER.md +3 -3
- package/atris/team/launcher/MEMBER.md +4 -4
- package/atris/team/navigator/MEMBER.md +3 -3
- package/atris/team/researcher/MEMBER.md +2 -2
- package/atris/team/validator/MEMBER.md +1 -1
- package/atris.md +14 -0
- package/ax +153 -23
- package/bin/atris.js +78 -30
- package/commands/aeo.js +4 -4
- package/commands/agents.js +1 -1
- package/commands/align.js +12 -12
- package/commands/analytics.js +3 -3
- package/commands/app.js +5 -5
- package/commands/ask.js +175 -0
- package/commands/auth.js +59 -31
- package/commands/autoland.js +29 -5
- package/commands/autopilot-front.js +6 -6
- package/commands/autopilot.js +90 -90
- package/commands/avail.js +5 -5
- package/commands/bench.js +63 -1
- package/commands/browse.js +2 -2
- package/commands/business.js +12 -12
- package/commands/card.js +2 -2
- package/commands/clean.js +13 -13
- package/commands/close.js +3 -3
- package/commands/compile.js +13 -13
- package/commands/computer.js +94 -27
- package/commands/console.js +5 -5
- package/commands/decide.js +1 -1
- package/commands/deck.js +9 -9
- package/commands/drive.js +4 -4
- package/commands/engine.js +173 -19
- package/commands/errors.js +3 -3
- package/commands/experiments.js +18 -1
- package/commands/feed.js +2 -2
- package/commands/feedback.js +4 -4
- package/commands/fleet.js +12 -12
- package/commands/game.js +1 -1
- package/commands/guide.js +49 -0
- package/commands/improve.js +19 -17
- package/commands/init.js +67 -19
- package/commands/interview.js +7 -7
- package/commands/land.js +40 -15
- package/commands/learn.js +216 -50
- package/commands/lesson.js +2 -2
- package/commands/log.js +1 -1
- package/commands/loops.js +4 -4
- package/commands/member.js +75 -50
- package/commands/mission.js +90 -82
- package/commands/one-lap.js +1 -1
- package/commands/pack.js +74 -8
- package/commands/plugin.js +6 -6
- package/commands/probe.js +9 -9
- package/commands/pull.js +35 -35
- package/commands/pulse.js +1 -1
- package/commands/push.js +21 -21
- package/commands/rainmaker.js +1 -1
- package/commands/recap.js +7 -7
- package/commands/reel.js +2 -2
- package/commands/release.js +3 -3
- package/commands/report.js +1 -1
- package/commands/review.js +5 -5
- package/commands/revisions.js +1 -1
- package/commands/run.js +1 -1
- package/commands/scout.js +3 -3
- package/commands/security-review.js +7 -7
- package/commands/serve.js +9 -9
- package/commands/setup.js +1 -1
- package/commands/sign.js +4 -4
- package/commands/signup.js +4 -4
- package/commands/site.js +8 -3
- package/commands/skill.js +6 -6
- package/commands/slop.js +18 -18
- package/commands/social.js +3 -3
- package/commands/soul.js +14 -14
- package/commands/spaceship.js +1 -1
- package/commands/status.js +2 -2
- package/commands/strings.js +13 -13
- package/commands/study.js +4 -4
- package/commands/sync.js +8 -8
- package/commands/task.js +50 -25
- package/commands/teach.js +2 -2
- package/commands/team.js +1 -1
- package/commands/theme.js +2 -2
- package/commands/tree.js +34 -0
- package/commands/truth.js +7 -7
- package/commands/verify.js +12 -12
- package/commands/visualize.js +2 -2
- package/commands/workflow.js +101 -48
- package/commands/worktree.js +1 -1
- package/commands/write.js +9 -9
- package/commands/x-search.js +93 -22
- package/commands/xp.js +6 -3
- package/commands/youtube.js +1019 -123
- package/lib/accept-verify-gate.js +3 -3
- package/lib/activity-stream.js +1 -1
- package/lib/auto-accept-certified.js +28 -12
- package/lib/autoland.js +6 -6
- package/lib/bench/engines.js +59 -8
- package/lib/bench/report.js +1 -1
- package/lib/bench/runner.js +204 -5
- package/lib/bench/tree-render.js +76 -0
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/claude-boot-block.js +2 -0
- package/lib/deck-compose.js +1 -1
- package/lib/deck-history.js +2 -2
- package/lib/deck-layout.js +1 -1
- package/lib/deck-review.js +5 -5
- package/lib/deck-schema.js +1 -1
- package/lib/default-verifier.js +1 -1
- package/lib/developer-api.js +3 -0
- package/lib/engine-ask.js +45 -7
- package/lib/engine-registry.js +15 -2
- package/lib/file-ops.js +1 -1
- package/lib/fleet.js +71 -35
- package/lib/html-render.js +1 -1
- package/lib/intents.js +172 -0
- package/lib/journal.js +1 -1
- package/lib/known-commands.js +2 -2
- package/lib/land-green.js +364 -0
- package/lib/lesson-contradiction.js +2 -2
- package/lib/lesson-preflight.js +1 -1
- package/lib/manifest.js +5 -5
- package/lib/member-alive.js +9 -5
- package/lib/memory-view.js +3 -3
- package/lib/mission-protected-lane.js +1 -1
- package/lib/mission-root.js +2 -2
- package/lib/orb-context.js +10 -10
- package/lib/pack-capabilities.js +861 -3
- package/lib/permission-grants.js +2 -2
- package/lib/policy-lessons.js +6 -6
- package/lib/pulse.js +8 -8
- package/lib/receipt-evidence.js +1 -1
- package/lib/reel.js +1 -1
- package/lib/review-integrity.js +1 -1
- package/lib/runner-command.js +35 -5
- package/lib/scorecard.js +4 -4
- package/lib/security-scan.js +5 -5
- package/lib/self-drive.js +1 -1
- package/lib/site-publish.js +372 -0
- package/lib/site.js +1 -1
- package/lib/slides-deck.js +2 -2
- package/lib/state-detection.js +2 -2
- package/lib/sync-telemetry.js +2 -2
- package/lib/task-db.js +17 -10
- package/lib/task-explanation.js +1 -1
- package/lib/task-proof.js +2 -2
- package/lib/task-receipt.js +3 -3
- package/lib/theme.js +12 -12
- package/lib/todo-fallback.js +3 -3
- package/lib/todo-sections.js +1 -1
- package/lib/tree-hash.js +79 -0
- package/lib/usage.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/workspace-safety.js +4 -4
- package/lib/workspace-scaffold.js +3 -3
- package/package.json +1 -1
- package/scripts/det/concierge-bakeoff.sh +26 -0
- package/scripts/det/ytnotes +82 -5
- package/scripts/det/ytquote-repair.js +0 -1
- package/scripts/det/ytrail-race.js +0 -5
- package/scripts/member-operate.mjs +52 -14
- package/scripts/outbound-artifact-gate.js +0 -3
- package/utils/auth.js +47 -9
- package/utils/config.js +0 -1
- package/scripts/det/checklist-score.js +0 -191
package/commands/pack.js
CHANGED
|
@@ -25,6 +25,9 @@ const {
|
|
|
25
25
|
finalizePackRunReceipt,
|
|
26
26
|
receiptDirectory,
|
|
27
27
|
classifyPackRunLifecycle,
|
|
28
|
+
assessPackRecoveryJournal,
|
|
29
|
+
claimPackRecoveryParent,
|
|
30
|
+
confirmPackRecoveryClaim,
|
|
28
31
|
} = require('../lib/pack-capabilities');
|
|
29
32
|
|
|
30
33
|
const REGISTRY_TIMEOUT_MS = 60000;
|
|
@@ -101,7 +104,7 @@ const PACKET = {
|
|
|
101
104
|
|
|
102
105
|
// ── definitions, not state ────────────────────────────────────────────────
|
|
103
106
|
// A packet carries what a thing IS, never what it is currently DOING. The
|
|
104
|
-
// definition files travel
|
|
107
|
+
// definition files travel: MEMBER.md, MISSION.md, SKILL.md, SOUL.md,
|
|
105
108
|
// README.md, wiki pages, policies. The running state a live workspace keeps
|
|
106
109
|
// beside them stays home: a stranger cannot use someone else's standup notes,
|
|
107
110
|
// and project-management exhaust is most of the file count.
|
|
@@ -160,7 +163,7 @@ function showHelp() {
|
|
|
160
163
|
console.log(' atris pack seal <dir> [--type <t>] [--entrypoint <file>]');
|
|
161
164
|
console.log(' atris pack publish [--dir atris] [--slug <slug>] [--author "<name>"] [--notes "..."] [--visibility public|unlisted|private] [--minor|--major] [--out <file.zip>] [--push] [--dry-run] [--allow-secrets]');
|
|
162
165
|
console.log(' atris pack install <file.zip|url|slug> [--dir <target>] [--force]');
|
|
163
|
-
console.log(' atris pack run <slug|dir> [--dir <target>] [--input <file>] [--cloud] [--force] [--trust] [--grant <capability>]');
|
|
166
|
+
console.log(' atris pack run <slug|dir> [--dir <target>] [--input <file>] [--cloud] [--force] [--trust] [--grant <capability>] [--recover <receipt.json>]');
|
|
164
167
|
console.log(' atris pack runs [--dir <receipt-dir>] [--limit <n>] [--json]');
|
|
165
168
|
console.log(' atris pack share <slug> --for "<Name>" [--days 30]');
|
|
166
169
|
console.log(' atris pack share <slug> --list');
|
|
@@ -1508,7 +1511,7 @@ function formatBytes(value) {
|
|
|
1508
1511
|
}
|
|
1509
1512
|
|
|
1510
1513
|
// What a stranger actually receives, in one glance. The tree below is the
|
|
1511
|
-
// detail; this is the shape
|
|
1514
|
+
// detail; this is the shape, enough to see that the wiki, the skills, the
|
|
1512
1515
|
// members and the policies are all still in the box.
|
|
1513
1516
|
function printPacketComposition(entries) {
|
|
1514
1517
|
const byTop = new Map();
|
|
@@ -2254,7 +2257,7 @@ async function installPack(rawArgs, cwd = process.cwd(), options = {}) {
|
|
|
2254
2257
|
|
|
2255
2258
|
// ── pack run ────────────────────────────────────────────────────────────────
|
|
2256
2259
|
// Install is half a product: it leaves a folder and a suggestion. `pack run`
|
|
2257
|
-
// is the other half
|
|
2260
|
+
// is the other half: packet in, running workspace out.
|
|
2258
2261
|
//
|
|
2259
2262
|
// It defaults to LOCAL on purpose. The cloud path is gated: activating a
|
|
2260
2263
|
// business workspace needs auth, an existing business record, and a paid plan
|
|
@@ -2450,7 +2453,7 @@ function printCapabilityTrustCard(policy, trust, receiptPath, userDenyRuleCount
|
|
|
2450
2453
|
if (options.operatorInput) {
|
|
2451
2454
|
console.log(` operator input: ${options.operatorInput.bytes} bytes injected; source path withheld from the pack and receipt`);
|
|
2452
2455
|
}
|
|
2453
|
-
console.log(` host shell: ${policy.grantedCapabilities.includes('host.shell') ? 'GRANTED
|
|
2456
|
+
console.log(` host shell: ${policy.grantedCapabilities.includes('host.shell') ? 'GRANTED \u2014 Bash can reach host files and network' : 'denied'}`);
|
|
2454
2457
|
console.log(' receipt coverage: Atris hook tool events; direct slash-skill invocations and later Claude or policy denials may not appear');
|
|
2455
2458
|
console.log(` receipt: ${receiptPath}`);
|
|
2456
2459
|
}
|
|
@@ -2530,7 +2533,13 @@ function startPackLocal(packDir, deps = {}, options = {}) {
|
|
|
2530
2533
|
? process.stdin.isTTY !== true
|
|
2531
2534
|
: deps.nonInteractive === true);
|
|
2532
2535
|
const start = deps.computerLocal || require('./computer').computerLocal;
|
|
2533
|
-
const
|
|
2536
|
+
const recovery = options.recovery || null;
|
|
2537
|
+
let wrappedOpeningPrompt = packOpeningInstruction(openingPrompt, operatorInput);
|
|
2538
|
+
if (recovery && Array.isArray(recovery.protectedFiles) && recovery.protectedFiles.length) {
|
|
2539
|
+
const names = recovery.protectedFiles.map((entry) => entry.target).join(', ');
|
|
2540
|
+
const note = `This is a recovered run. These completed files are protected and must not be rewritten: ${names}. Continue with other files.`;
|
|
2541
|
+
wrappedOpeningPrompt = wrappedOpeningPrompt ? `${wrappedOpeningPrompt}\n\n${note}` : note;
|
|
2542
|
+
}
|
|
2534
2543
|
const runnerArgs = [];
|
|
2535
2544
|
let pluginDir = null;
|
|
2536
2545
|
let receipt = null;
|
|
@@ -2556,6 +2565,7 @@ function startPackLocal(packDir, deps = {}, options = {}) {
|
|
|
2556
2565
|
packSkillsPluginLoaded: Boolean(pluginDir),
|
|
2557
2566
|
nonInteractive,
|
|
2558
2567
|
operatorInput: operatorInput ? { bytes: operatorInput.bytes, sha256: operatorInput.sha256 } : null,
|
|
2568
|
+
recovery,
|
|
2559
2569
|
});
|
|
2560
2570
|
runnerArgs.push(...buildClaudeCapabilityArgs(capabilityPolicy, { trust, userDenyRules, nonInteractive }));
|
|
2561
2571
|
runnerOptions.runnerEnv = {
|
|
@@ -2563,11 +2573,13 @@ function startPackLocal(packDir, deps = {}, options = {}) {
|
|
|
2563
2573
|
ATRIS_PACK_RECEIPT: receipt.receiptPath,
|
|
2564
2574
|
ATRIS_PACK_RECEIPT_EVENTS: receipt.eventsPath,
|
|
2565
2575
|
ATRIS_PACK_GRANTED_CAPABILITIES: JSON.stringify(capabilityPolicy.grantedCapabilities),
|
|
2576
|
+
ATRIS_PACK_PROTECTED_FILES: JSON.stringify(recovery && recovery.protectedFiles ? recovery.protectedFiles : []),
|
|
2566
2577
|
CLAUDE_CODE_DISABLE_CLAUDE_MDS: '1',
|
|
2567
2578
|
CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1',
|
|
2568
2579
|
CLAUDE_CODE_SKIP_PROMPT_HISTORY: '1',
|
|
2569
2580
|
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB: '1',
|
|
2570
2581
|
};
|
|
2582
|
+
if (typeof options.onReceipt === 'function') options.onReceipt(receipt);
|
|
2571
2583
|
runnerOptions.cleanupPaths = pluginDir ? [pluginDir] : [];
|
|
2572
2584
|
runnerOptions.onRunnerExit = ({ status, signal }) => {
|
|
2573
2585
|
appendReceiptEvent(receipt.eventsPath, {
|
|
@@ -2579,6 +2591,10 @@ function startPackLocal(packDir, deps = {}, options = {}) {
|
|
|
2579
2591
|
nonInteractive,
|
|
2580
2592
|
operatorInput,
|
|
2581
2593
|
});
|
|
2594
|
+
if (recovery) {
|
|
2595
|
+
const names = (recovery.protectedFiles || []).map((entry) => entry.target).join(', ');
|
|
2596
|
+
console.log(`recovery: continuing from ${recovery.parentRunId} with ${(recovery.protectedFiles || []).length} protected file(s): ${names}.`);
|
|
2597
|
+
}
|
|
2582
2598
|
} else {
|
|
2583
2599
|
const skillsDir = path.join(packDir, 'skills');
|
|
2584
2600
|
const hasShippedSkill = fs.existsSync(skillsDir)
|
|
@@ -2693,6 +2709,10 @@ async function runPack(rawArgs, cwd = process.cwd(), options = {}) {
|
|
|
2693
2709
|
const force = takeFlag(args, '--force');
|
|
2694
2710
|
const trust = takeFlag(args, '--trust');
|
|
2695
2711
|
const grants = takeValues(args, '--grant');
|
|
2712
|
+
const recoverArg = takeValue(args, '--recover');
|
|
2713
|
+
if (recoverArg !== null && (!recoverArg || !String(recoverArg).trim())) {
|
|
2714
|
+
throw new Error('pack run --recover needs a receipt path; explicit empty values never fall through to a fresh run');
|
|
2715
|
+
}
|
|
2696
2716
|
if (args.length) throw new Error(`unknown pack run argument: ${args.join(' ')}`);
|
|
2697
2717
|
|
|
2698
2718
|
const operatorInput = inputArg ? readPackRunInput(inputArg, cwd) : null;
|
|
@@ -2710,6 +2730,7 @@ async function runPack(rawArgs, cwd = process.cwd(), options = {}) {
|
|
|
2710
2730
|
packDir = path.resolve(cwd, source);
|
|
2711
2731
|
manifest = assertPacketDir(packDir, cwd);
|
|
2712
2732
|
} else {
|
|
2733
|
+
if (recoverArg !== null) throw new Error('pack run --recover needs an existing pack directory, not a slug to install');
|
|
2713
2734
|
packDir = path.resolve(cwd, targetArg || slugify(source));
|
|
2714
2735
|
const alreadyThere = !force && fs.existsSync(path.join(packDir, 'pack.json'));
|
|
2715
2736
|
if (alreadyThere) {
|
|
@@ -2735,6 +2756,36 @@ async function runPack(rawArgs, cwd = process.cwd(), options = {}) {
|
|
|
2735
2756
|
);
|
|
2736
2757
|
}
|
|
2737
2758
|
if (capabilityPolicy.status === 'enforced') assertPackExecutionTree(packDir);
|
|
2759
|
+
let recovery = null;
|
|
2760
|
+
let claimPath = null;
|
|
2761
|
+
let childReceiptPath = null;
|
|
2762
|
+
if (recoverArg !== null) {
|
|
2763
|
+
if (cloud) throw new Error('pack run --recover is local-only; cloud recovery is not supported');
|
|
2764
|
+
if (capabilityPolicy.status !== 'enforced') {
|
|
2765
|
+
throw new Error('pack run --recover needs a declared capability ceiling; legacy packs cannot be recovered');
|
|
2766
|
+
}
|
|
2767
|
+
if (capabilityPolicy.grantedCapabilities.includes('host.shell')) {
|
|
2768
|
+
throw new Error('pack run --recover cannot grant host.shell; shell effects cannot be reasoned about');
|
|
2769
|
+
}
|
|
2770
|
+
const parentReceiptPath = path.resolve(cwd, recoverArg);
|
|
2771
|
+
const childReceiptDir = receiptDirectory({ receiptDir: deps.packRunReceiptDir });
|
|
2772
|
+
const assessment = assessPackRecoveryJournal({
|
|
2773
|
+
packDir,
|
|
2774
|
+
manifest,
|
|
2775
|
+
policy: capabilityPolicy,
|
|
2776
|
+
parentReceiptPath,
|
|
2777
|
+
operatorInput,
|
|
2778
|
+
childReceiptDir,
|
|
2779
|
+
});
|
|
2780
|
+
// Claimed before launch so a parent backs at most one child. A crash
|
|
2781
|
+
// from here on leaves a pending claim that needs manual review.
|
|
2782
|
+
claimPath = claimPackRecoveryParent(parentReceiptPath, assessment.parentRunId);
|
|
2783
|
+
recovery = {
|
|
2784
|
+
parentRunId: assessment.parentRunId,
|
|
2785
|
+
parentReceipt: parentReceiptPath,
|
|
2786
|
+
protectedFiles: assessment.protectedFiles,
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2738
2789
|
if (cloud) return startPackCloud(packDir, displayTarget, deps, { capabilityPolicy });
|
|
2739
2790
|
if (trust && capabilityPolicy.status === 'legacy') {
|
|
2740
2791
|
throw new Error(
|
|
@@ -2749,19 +2800,34 @@ async function runPack(rawArgs, cwd = process.cwd(), options = {}) {
|
|
|
2749
2800
|
}
|
|
2750
2801
|
console.log(`starting local computer in ${displayTarget}`);
|
|
2751
2802
|
if (capabilityPolicy.status === 'legacy') {
|
|
2752
|
-
console.log('capabilities: LEGACY
|
|
2803
|
+
console.log('capabilities: LEGACY, this pack declares no enforceable capability ceiling.');
|
|
2753
2804
|
}
|
|
2754
2805
|
if (!trust && capabilityPolicy.status === 'legacy') {
|
|
2755
2806
|
console.log('permission prompts are on because this legacy pack has no capability ceiling.');
|
|
2756
2807
|
console.log('for a bounded read-only run, add --grant pack.read; add --trust only to pre-approve that ceiling.');
|
|
2757
2808
|
}
|
|
2758
|
-
|
|
2809
|
+
const code = startPackLocal(packDir, deps, {
|
|
2759
2810
|
trust,
|
|
2760
2811
|
openingPrompt,
|
|
2761
2812
|
operatorInput,
|
|
2762
2813
|
capabilityPolicy,
|
|
2763
2814
|
manifest,
|
|
2815
|
+
recovery,
|
|
2816
|
+
onReceipt: (receipt) => {
|
|
2817
|
+
childReceiptPath = receipt.receiptPath;
|
|
2818
|
+
if (claimPath) confirmPackRecoveryClaim(claimPath, receipt.receiptPath);
|
|
2819
|
+
},
|
|
2764
2820
|
});
|
|
2821
|
+
if (claimPath && childReceiptPath) {
|
|
2822
|
+
try {
|
|
2823
|
+
confirmPackRecoveryClaim(claimPath, childReceiptPath);
|
|
2824
|
+
} catch {
|
|
2825
|
+
// Already confirmed in onReceipt before the runner ran; the real
|
|
2826
|
+
// console exits the process there, so this is only a backstop for
|
|
2827
|
+
// injected runners that return normally.
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
return code;
|
|
2765
2831
|
}
|
|
2766
2832
|
|
|
2767
2833
|
async function updatePack(rawArgs, cwd = process.cwd(), options = {}) {
|
package/commands/plugin.js
CHANGED
|
@@ -44,7 +44,7 @@ function generateManifest(skills, projectDir) {
|
|
|
44
44
|
return {
|
|
45
45
|
name: 'atris-workspace',
|
|
46
46
|
version: pkg.version || '1.0.0',
|
|
47
|
-
description: `Atris workspace skills for Cowork
|
|
47
|
+
description: `Atris workspace skills for Cowork: ${skills.length} integrations (email, calendar, slack, drive, notion, and more)`,
|
|
48
48
|
author: {
|
|
49
49
|
name: typeof pkg.author === 'string' ? pkg.author : (pkg.author?.name || 'Atris')
|
|
50
50
|
},
|
|
@@ -89,7 +89,7 @@ If not logged in, run:
|
|
|
89
89
|
atris login
|
|
90
90
|
\`\`\`
|
|
91
91
|
|
|
92
|
-
This is interactive
|
|
92
|
+
This is interactive, the user will need to complete OAuth in their browser and paste the CLI code. Guide them through it.
|
|
93
93
|
|
|
94
94
|
## Step 3: Check integration status
|
|
95
95
|
|
|
@@ -149,7 +149,7 @@ Brings Atris CLI skills into the Cowork desktop app.
|
|
|
149
149
|
|
|
150
150
|
1. Install this plugin in Cowork
|
|
151
151
|
2. Run \`/atris-setup\` to authenticate and connect integrations
|
|
152
|
-
3. Start using skills
|
|
152
|
+
3. Start using skills: they trigger automatically based on your requests
|
|
153
153
|
|
|
154
154
|
## Included Skills (${skills.length})
|
|
155
155
|
|
|
@@ -279,7 +279,7 @@ function buildPlugin(...args) {
|
|
|
279
279
|
console.log('');
|
|
280
280
|
|
|
281
281
|
} finally {
|
|
282
|
-
// Clean up temp dir (only if ZIP succeeded
|
|
282
|
+
// Clean up temp dir (only if ZIP succeeded, we skip cleanup on error above)
|
|
283
283
|
if (fs.existsSync(tempDir)) {
|
|
284
284
|
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (e) { /* ignore */ }
|
|
285
285
|
}
|
|
@@ -346,7 +346,7 @@ function publishPlugin(...args) {
|
|
|
346
346
|
process.exit(1);
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
// Find the marketplace repo
|
|
349
|
+
// Find the marketplace repo: check sibling dir or --repo flag
|
|
350
350
|
let marketplaceDir = null;
|
|
351
351
|
for (const arg of args) {
|
|
352
352
|
if (typeof arg === 'string' && arg.startsWith('--repo=')) {
|
|
@@ -432,7 +432,7 @@ function publishPlugin(...args) {
|
|
|
432
432
|
execSync('git add -A', { cwd: marketplaceDir, stdio: 'pipe' });
|
|
433
433
|
const status = execSync('git status --porcelain', { cwd: marketplaceDir, encoding: 'utf8' }).trim();
|
|
434
434
|
if (!status) {
|
|
435
|
-
console.log('✓ No changes
|
|
435
|
+
console.log('✓ No changes, marketplace already up to date.');
|
|
436
436
|
return;
|
|
437
437
|
}
|
|
438
438
|
execSync(`git commit -m "chore: Update atris-workspace plugin to v${version}"`, {
|
package/commands/probe.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// atris probe
|
|
1
|
+
// atris probe: chat-lane probe (TRR-22): one REAL /atris2/turn over the full
|
|
2
2
|
// tool relay, exactly as the surfaces run it. Port of terrace's
|
|
3
|
-
// atris/bin/relay-probe and atris/bin/calendar-probe
|
|
3
|
+
// atris/bin/relay-probe and atris/bin/calendar-probe, so keep the op tables in
|
|
4
4
|
// lockstep across iOS (votd/GMModeAPI.swift Atris2ToolRelay), web
|
|
5
5
|
// (atrisos-web orbToolRelay.ts), Obelisk (atris2LocalFileOp.cjs), and the
|
|
6
6
|
// terrace probes.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// PASS = >=1 relayed op (with --calendar: >=1 calendar cli op) AND a non-empty
|
|
16
16
|
// final answer with no dead-end marker; otherwise prints the marker in the
|
|
17
|
-
// receipt line and exits 1
|
|
17
|
+
// receipt line and exits 1. a FAIL naming the dead-end is the instrument
|
|
18
18
|
// working. The receipt line is journal-ready.
|
|
19
19
|
//
|
|
20
20
|
// Usage:
|
|
@@ -32,7 +32,7 @@ const { loadCredentials } = require('../utils/auth');
|
|
|
32
32
|
const { buildToolResultBody } = require('../lib/tool-result-encode');
|
|
33
33
|
|
|
34
34
|
// G2's honest blocked message (tool_policy_bench.MAX_TURNS_EXHAUSTED_MESSAGE)
|
|
35
|
-
// starts with this
|
|
35
|
+
// starts with this. an answer that is only this marker is a dead-end.
|
|
36
36
|
const MAX_TURNS_MARKER = 'i ran out of tool budget for this turn';
|
|
37
37
|
|
|
38
38
|
function workspaceLabel(businessId) {
|
|
@@ -61,7 +61,7 @@ function normalizeBash(cmd, label) {
|
|
|
61
61
|
|
|
62
62
|
// No write/edit file ops, and the `..` guard only covers args.path. The
|
|
63
63
|
// `bash` op still executes whatever command the model sends, verbatim, on
|
|
64
|
-
// the remote ai-computer
|
|
64
|
+
// the remote ai-computer, that is the production relay contract and the
|
|
65
65
|
// table must stay in lockstep with it, so this probe is NOT read-only.
|
|
66
66
|
function fileOpCommand(args, label) {
|
|
67
67
|
const op = String(args.type || '').toLowerCase();
|
|
@@ -78,7 +78,7 @@ function fileOpCommand(args, label) {
|
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Port of Atris2ToolRelay.atrisCLICommand (GMModeAPI.swift)
|
|
81
|
+
// Port of Atris2ToolRelay.atrisCLICommand (GMModeAPI.swift): op → `atris …`.
|
|
82
82
|
const SIMPLE_CLI_OPS = {
|
|
83
83
|
status: ['atris.md'],
|
|
84
84
|
integrations_status: ['integrations'],
|
|
@@ -122,7 +122,7 @@ function atrisCliCommand(args) {
|
|
|
122
122
|
|
|
123
123
|
function atrisCliResult(command, term) {
|
|
124
124
|
// a terminal response without a numeric exit_code is a broken endpoint,
|
|
125
|
-
// not a success
|
|
125
|
+
// not a success. never let it masquerade as ok with empty stdout
|
|
126
126
|
const code = typeof term.exit_code === 'number' ? term.exit_code : -1;
|
|
127
127
|
const out = {
|
|
128
128
|
schema: 'atris.local_cli_result.v1',
|
|
@@ -227,7 +227,7 @@ function parseArgs(argv) {
|
|
|
227
227
|
|
|
228
228
|
// Core /atris2/turn client: one streamed turn over the full local tool relay.
|
|
229
229
|
// Shared by `atris probe` (the instrument) and `atris mission run --runner atris2`
|
|
230
|
-
// (the worker). Transport-level outcome only
|
|
230
|
+
// (the worker). Transport-level outcome only. callers apply their own
|
|
231
231
|
// pass/fail policy on top of the returned fields.
|
|
232
232
|
async function runAtris2Turn(opts = {}) {
|
|
233
233
|
const {
|
|
@@ -454,7 +454,7 @@ async function probeCommand(argv) {
|
|
|
454
454
|
const detail = ok
|
|
455
455
|
? `answer: ${resultText.slice(0, 80).replace(/\n/g, ' ')}`
|
|
456
456
|
: `dead-end: ${deadEnd}`;
|
|
457
|
-
const line = `- **atris-probe** \`${stamp}\`
|
|
457
|
+
const line = `- **atris-probe** \`${stamp}\` \u2014 ${where} · ${a.model}`
|
|
458
458
|
+ (member ? ` · member=${member}` : '')
|
|
459
459
|
+ (engine ? ` · engine=${engine}` : '')
|
|
460
460
|
+ ` · ${ok ? 'PASS' : 'FAIL'} · ${secs}s · tools=${toolsRun}`
|
package/commands/pull.js
CHANGED
|
@@ -259,14 +259,14 @@ async function pullBusiness(slug) {
|
|
|
259
259
|
// Determine output directory.
|
|
260
260
|
//
|
|
261
261
|
// We only reuse the current working directory when we can prove it's the
|
|
262
|
-
// correct workspace for THIS business
|
|
262
|
+
// correct workspace for THIS business, i.e. it has a `.atris/business.json`
|
|
263
263
|
// whose slug matches `slug`. Any other signal (a stray `atris/` folder, a
|
|
264
264
|
// business.json for a different business, etc.) is NOT enough: pulling
|
|
265
265
|
// one business on top of another business workspace would mix two businesses into
|
|
266
266
|
// one directory and write one manifest over the other (or vice
|
|
267
267
|
// versa), causing the next sync to do strange things.
|
|
268
268
|
//
|
|
269
|
-
// Fallback: create a fresh ./{slug}/ subdir. Always safe
|
|
269
|
+
// Fallback: create a fresh ./{slug}/ subdir. Always safe, even if cwd is
|
|
270
270
|
// $HOME or /tmp, we land in a dedicated subfolder.
|
|
271
271
|
const intoIdx = process.argv.indexOf('--into');
|
|
272
272
|
let outputDir;
|
|
@@ -281,7 +281,7 @@ async function pullBusiness(slug) {
|
|
|
281
281
|
const cwdSlug = biz.slug || biz.name;
|
|
282
282
|
cwdMatchesSlug = cwdSlug === slug;
|
|
283
283
|
} catch {
|
|
284
|
-
// Corrupt business.json
|
|
284
|
+
// Corrupt business.json, ignore, treat as no match.
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
outputDir = cwdMatchesSlug ? process.cwd() : path.join(process.cwd(), slug);
|
|
@@ -289,7 +289,7 @@ async function pullBusiness(slug) {
|
|
|
289
289
|
|
|
290
290
|
// Auto-relocate if the resolved outputDir is dangerous ($HOME, /, /Users,
|
|
291
291
|
// system dirs, reserved home folders). Non-technical users shouldn't have
|
|
292
|
-
// to know about mkdir/cd
|
|
292
|
+
// to know about mkdir/cd, atris picks a safe subdir and tells them.
|
|
293
293
|
// Paired with the manifest-scoped sweep below, this makes it impossible
|
|
294
294
|
// for a stray cwd to cause atris to delete user files.
|
|
295
295
|
({ dir: outputDir } = resolveSafeOutputDir(outputDir, { slug, op: 'pull into' }));
|
|
@@ -298,7 +298,7 @@ async function pullBusiness(slug) {
|
|
|
298
298
|
// narrowed bound workspace pulls to atris/, which made staging snapshots and
|
|
299
299
|
// the real workspace diverge while both reported "synced".
|
|
300
300
|
|
|
301
|
-
// Resolve business ID
|
|
301
|
+
// Resolve business ID, always refresh from API to avoid stale workspace_id
|
|
302
302
|
let businessId, workspaceId, businessName, resolvedSlug;
|
|
303
303
|
let localSlug = slug;
|
|
304
304
|
const businesses = loadBusinesses();
|
|
@@ -347,14 +347,14 @@ async function pullBusiness(slug) {
|
|
|
347
347
|
process.exit(1);
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
// Telemetry helper
|
|
350
|
+
// Telemetry helper, captures wall-clock time including wake.
|
|
351
351
|
let _coldWake = false;
|
|
352
352
|
const emit = (outcome, extras = {}) =>
|
|
353
353
|
emitSyncEvent(creds.token, businessId, workspaceId, 'pull', outcome, elapsedMs(), extras);
|
|
354
354
|
|
|
355
355
|
// Auto-wake the EC2 computer if --auto-wake is set.
|
|
356
356
|
// Without this, pull silently serves stale data from agent_files cache when
|
|
357
|
-
// the computer is asleep
|
|
357
|
+
// the computer is asleep, the bug that confused us all night.
|
|
358
358
|
const autoWake = process.argv.includes('--auto-wake');
|
|
359
359
|
if (autoWake) {
|
|
360
360
|
const statusResult = await apiRequestJson(`/business/${businessId}/ai-computer/status`, { method: 'GET', token: creds.token });
|
|
@@ -415,7 +415,7 @@ async function pullBusiness(slug) {
|
|
|
415
415
|
const pathsParam = onlyPrefixes ? `&paths=${encodeURIComponent(onlyPrefixes.map(p => p.replace(/\/$/, '')).join(','))}` : '';
|
|
416
416
|
|
|
417
417
|
if (hasManifest) {
|
|
418
|
-
// Phase 1: fetch hashes only (fast
|
|
418
|
+
// Phase 1: fetch hashes only (fast, no file content transferred)
|
|
419
419
|
const hashUrl = `/business/${businessId}/workspaces/${workspaceId}/snapshot?include_content=false${pathsParam}`;
|
|
420
420
|
const hashResult = await apiRequestJson(hashUrl, { method: 'GET', token: creds.token, timeoutMs });
|
|
421
421
|
|
|
@@ -438,13 +438,13 @@ async function pullBusiness(slug) {
|
|
|
438
438
|
if (changedPaths.length === 0) {
|
|
439
439
|
clearInterval(loading);
|
|
440
440
|
process.stdout.write(`\r Checked ${Object.keys(remoteHashes).length} files in ${Math.floor((Date.now() - startTime) / 1000)}s.${' '.repeat(10)}\n`);
|
|
441
|
-
// Still need full result for diff logic below
|
|
441
|
+
// Still need full result for diff logic below, build it from hash-only data
|
|
442
442
|
result = { ok: true, data: { files: hashResult.data.files } };
|
|
443
443
|
} else {
|
|
444
444
|
// Phase 2: fetch ONLY changed files via batch endpoint (not full snapshot)
|
|
445
445
|
clearInterval(loading);
|
|
446
446
|
const checkSec = Math.floor((Date.now() - startTime) / 1000);
|
|
447
|
-
console.log(`\r Checked in ${checkSec}s
|
|
447
|
+
console.log(`\r Checked in ${checkSec}s, ${changedPaths.length} changed, ${Object.keys(remoteHashes).length - changedPaths.length} unchanged.${' '.repeat(10)}`);
|
|
448
448
|
|
|
449
449
|
const startPhase2 = Date.now();
|
|
450
450
|
const loading2 = setInterval(() => {
|
|
@@ -452,7 +452,7 @@ async function pullBusiness(slug) {
|
|
|
452
452
|
process.stdout.write(`\r Fetching ${changedPaths.length} changed files... ${spinner[spinIdx++ % 4]} ${elapsed}s`);
|
|
453
453
|
}, 250);
|
|
454
454
|
|
|
455
|
-
// Try batch file read first (fast
|
|
455
|
+
// Try batch file read first (fast, only changed files)
|
|
456
456
|
const batchUrl = `/business/${businessId}/workspaces/${workspaceId}/files/batch`;
|
|
457
457
|
const batchResult = await apiRequestJson(batchUrl, {
|
|
458
458
|
method: 'POST',
|
|
@@ -477,7 +477,7 @@ async function pullBusiness(slug) {
|
|
|
477
477
|
process.stdout.write(`\r Fetched in ${fullSec}s.${' '.repeat(20)}\n`);
|
|
478
478
|
}
|
|
479
479
|
} else {
|
|
480
|
-
// Batch not available
|
|
480
|
+
// Batch not available, fall back to full snapshot
|
|
481
481
|
process.stdout.write(`\r Batch unavailable, fetching full snapshot...${' '.repeat(10)}\n`);
|
|
482
482
|
const contentUrl = `/business/${businessId}/workspaces/${workspaceId}/snapshot?include_content=true${pathsParam}`;
|
|
483
483
|
result = await apiRequestJson(contentUrl, { method: 'GET', token: creds.token, timeoutMs });
|
|
@@ -486,14 +486,14 @@ async function pullBusiness(slug) {
|
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
488
|
} else {
|
|
489
|
-
// Hash-only fetch failed
|
|
489
|
+
// Hash-only fetch failed, fall back to full snapshot
|
|
490
490
|
const fullUrl = `/business/${businessId}/workspaces/${workspaceId}/snapshot?include_content=true${pathsParam}`;
|
|
491
491
|
result = await apiRequestJson(fullUrl, { method: 'GET', token: creds.token, timeoutMs });
|
|
492
492
|
clearInterval(loading);
|
|
493
493
|
process.stdout.write(`\r Fetched in ${Math.floor((Date.now() - startTime) / 1000)}s.${' '.repeat(20)}\n`);
|
|
494
494
|
}
|
|
495
495
|
} else {
|
|
496
|
-
// First sync or --force
|
|
496
|
+
// First sync or --force, full snapshot with content
|
|
497
497
|
const snapshotUrl = `/business/${businessId}/workspaces/${workspaceId}/snapshot?include_content=true${pathsParam}`;
|
|
498
498
|
result = await apiRequestJson(snapshotUrl, { method: 'GET', token: creds.token, timeoutMs });
|
|
499
499
|
clearInterval(loading);
|
|
@@ -570,7 +570,7 @@ async function pullBusiness(slug) {
|
|
|
570
570
|
// Don't early-return: we still need to update the manifest so paths
|
|
571
571
|
// that USED to be in the scoped subtree but were deleted on cloud
|
|
572
572
|
// get evicted from the manifest. Without this, the next push freshness
|
|
573
|
-
// check would forever flag those paths as drift and demand a pull
|
|
573
|
+
// check would forever flag those paths as drift and demand a pull,
|
|
574
574
|
// but the pull would early-return again, creating a deadlock.
|
|
575
575
|
} else {
|
|
576
576
|
console.log(` Filtered to ${files.length} files matching: ${onlyPrefixes.join(', ')}`);
|
|
@@ -580,7 +580,7 @@ async function pullBusiness(slug) {
|
|
|
580
580
|
// Build remote file map {path: {hash, size}} and content map {path: content}.
|
|
581
581
|
//
|
|
582
582
|
// CRITICAL: smart-pull (hash-only fetch) returns files with `path`+`hash`+`size`
|
|
583
|
-
// but no `content`. Phase-2 batch fetch only adds content for CHANGED files
|
|
583
|
+
// but no `content`. Phase-2 batch fetch only adds content for CHANGED files,
|
|
584
584
|
// unchanged files stay hash-only. We must include hash-only entries in remoteFiles
|
|
585
585
|
// so threeWayCompare doesn't see them as missing-from-remote (deletedRemote).
|
|
586
586
|
// The previous version skipped any file without content, which caused every
|
|
@@ -598,16 +598,16 @@ async function pullBusiness(slug) {
|
|
|
598
598
|
// empty files masquerade as hash-only entries; they'd then be recorded in
|
|
599
599
|
// the manifest (with the empty-string hash) but never written to disk.
|
|
600
600
|
// A subsequent push would compare local (file missing) to manifest (file
|
|
601
|
-
// present) and try to delete the file from cloud
|
|
601
|
+
// present) and try to delete the file from cloud, silently undoing the
|
|
602
602
|
// very thing the user just pulled.
|
|
603
603
|
const hasContent = file.content !== null && file.content !== undefined && typeof file.content === 'string';
|
|
604
604
|
if (hasContent) {
|
|
605
|
-
// Full content available
|
|
605
|
+
// Full content available, hash from raw bytes (matches computeLocalHashes)
|
|
606
606
|
const rawBytes = Buffer.from(file.content, 'utf-8');
|
|
607
607
|
remoteFiles[normalizedPath] = { hash: crypto.createHash('sha256').update(rawBytes).digest('hex'), size: rawBytes.length };
|
|
608
608
|
remoteContent[normalizedPath] = file.content;
|
|
609
609
|
} else if (file.hash) {
|
|
610
|
-
// Hash-only entry from smart pull
|
|
610
|
+
// Hash-only entry from smart pull, trust the cloud-reported hash
|
|
611
611
|
remoteFiles[normalizedPath] = { hash: file.hash, size: file.size || 0 };
|
|
612
612
|
}
|
|
613
613
|
}
|
|
@@ -636,7 +636,7 @@ async function pullBusiness(slug) {
|
|
|
636
636
|
};
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
-
// If output dir is empty (fresh clone) or --force, treat as first sync
|
|
639
|
+
// If output dir is empty (fresh clone) or --force, treat as first sync, pull everything
|
|
640
640
|
const scopedLocalFiles = filterFilesToPullScope(localFiles);
|
|
641
641
|
const scopedRemoteFiles = filterFilesToPullScope(remoteFiles);
|
|
642
642
|
const scopedManifest = filterManifestToPullScope(manifest);
|
|
@@ -750,20 +750,20 @@ async function pullBusiness(slug) {
|
|
|
750
750
|
}
|
|
751
751
|
}
|
|
752
752
|
|
|
753
|
-
// FORCE MIRROR SWEEP
|
|
753
|
+
// FORCE MIRROR SWEEP, local must EXACTLY match cloud after a force pull.
|
|
754
754
|
// The threeWayCompare path with effectiveManifest=null only computes
|
|
755
755
|
// newLocal/conflicts/newRemote and never marks files as deletedRemote, so
|
|
756
756
|
// local-only files (created locally, never on cloud) survive a force pull.
|
|
757
757
|
// That breaks the "cloud is the source of truth" promise. Sweep them now.
|
|
758
758
|
//
|
|
759
|
-
// SAFETY GUARDS
|
|
759
|
+
// SAFETY GUARDS, without these the sweep can wipe an entire local copy:
|
|
760
760
|
// • Scope the sweep: when --only is set, only sweep paths INSIDE the
|
|
761
|
-
// prefix(es). Out-of-scope local files must be left alone
|
|
761
|
+
// prefix(es). Out-of-scope local files must be left alone, the user
|
|
762
762
|
// asked for a partial pull, not a workspace-wide reset.
|
|
763
763
|
// • Skip when remoteFiles is empty AND local has in-scope content: the
|
|
764
764
|
// snapshot endpoint has a known server-side bug where it returns 0
|
|
765
765
|
// files for healthy workspaces. If cloud reports empty but local has
|
|
766
|
-
// in-scope content we refuse to sweep
|
|
766
|
+
// in-scope content we refuse to sweep, the user can re-run with
|
|
767
767
|
// --keep-local and investigate, or run `atris align --hard` for an
|
|
768
768
|
// explicit nuke.
|
|
769
769
|
// • Skip files the server's snapshot filter hides. The warm runner's
|
|
@@ -786,11 +786,11 @@ async function pullBusiness(slug) {
|
|
|
786
786
|
|
|
787
787
|
// SAFETY: only sweep files atris previously wrote (recorded in the
|
|
788
788
|
// manifest). Local-only files that were never on cloud are the user's
|
|
789
|
-
// own
|
|
789
|
+
// own, atris didn't put them there, atris doesn't delete them. This
|
|
790
790
|
// makes it impossible for a stray cwd (e.g. $HOME) to cause atris to
|
|
791
791
|
// wipe ~/Library, ~/Downloads, etc.
|
|
792
792
|
//
|
|
793
|
-
// If there's no prior manifest (first pull), there's nothing to sweep
|
|
793
|
+
// If there's no prior manifest (first pull), there's nothing to sweep,
|
|
794
794
|
// threeWayCompare already handled newRemote/conflicts/newLocal, and we
|
|
795
795
|
// have no basis for claiming ownership of any local-only file.
|
|
796
796
|
const managedPaths = manifest && manifest.files
|
|
@@ -815,7 +815,7 @@ async function pullBusiness(slug) {
|
|
|
815
815
|
console.log(` - ${p.replace(/^\//, '')} not on cloud, removed locally`);
|
|
816
816
|
deleted++;
|
|
817
817
|
} catch {
|
|
818
|
-
// ignore
|
|
818
|
+
// ignore, file might already be gone
|
|
819
819
|
}
|
|
820
820
|
}
|
|
821
821
|
}
|
|
@@ -846,7 +846,7 @@ async function pullBusiness(slug) {
|
|
|
846
846
|
commitHash = headResult.data.commit;
|
|
847
847
|
}
|
|
848
848
|
} catch {
|
|
849
|
-
// Git might not be initialized yet
|
|
849
|
+
// Git might not be initialized yet, that's fine
|
|
850
850
|
}
|
|
851
851
|
|
|
852
852
|
if (failOnConflict && conflictCount > 0) {
|
|
@@ -878,7 +878,7 @@ async function pullBusiness(slug) {
|
|
|
878
878
|
// ANTI-WIPE GUARD: if cloud reported zero in-scope files but local still
|
|
879
879
|
// has in-scope content (i.e. the sweep refused), don't overwrite the
|
|
880
880
|
// manifest with empty data for the scoped subtree. The manifest is the
|
|
881
|
-
// authoritative record of what we last knew was on cloud
|
|
881
|
+
// authoritative record of what we last knew was on cloud, wiping it
|
|
882
882
|
// because of a transient empty snapshot would force every subsequent
|
|
883
883
|
// push to flag every file as drift. Better to leave the manifest stale
|
|
884
884
|
// than to record a never-actually-true "cloud is empty" state.
|
|
@@ -893,7 +893,7 @@ async function pullBusiness(slug) {
|
|
|
893
893
|
}
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
-
// Save manifest
|
|
896
|
+
// Save manifest, when using --only, merge into existing manifest so paths
|
|
897
897
|
// OUTSIDE the scoped prefix don't get dropped. Inside the scoped prefix,
|
|
898
898
|
// however, we must replace (not merge) so that files deleted on cloud
|
|
899
899
|
// since the last sync get evicted from the manifest. Without this, the
|
|
@@ -939,7 +939,7 @@ async function pullBusiness(slug) {
|
|
|
939
939
|
|
|
940
940
|
const skillFile = path.join(fullPath, 'SKILL.md');
|
|
941
941
|
if (fs.existsSync(skillFile)) {
|
|
942
|
-
// This is a leaf skill
|
|
942
|
+
// This is a leaf skill, wire it
|
|
943
943
|
const skillName = relPrefix ? `${relPrefix}-${entry}` : entry;
|
|
944
944
|
const symlinkPath = path.join(claudeSkillsDir, skillName);
|
|
945
945
|
const relativePath = path.relative(path.dirname(symlinkPath), fullPath);
|
|
@@ -982,7 +982,7 @@ async function pullBusiness(slug) {
|
|
|
982
982
|
}
|
|
983
983
|
}
|
|
984
984
|
|
|
985
|
-
// Telemetry
|
|
985
|
+
// Telemetry, only count files where content actually came over the wire
|
|
986
986
|
// (smart-pull skips unchanged files and just sends a hash). Counting all
|
|
987
987
|
// remoteFiles here would dwarf the real signal once smart-pull steady-state
|
|
988
988
|
// is normal (< 1% of files transferred per pull).
|
|
@@ -1048,12 +1048,12 @@ async function pullGeneralJournal(token, agentId) {
|
|
|
1048
1048
|
if (localContent.trim() === remoteContent.trim()) continue;
|
|
1049
1049
|
|
|
1050
1050
|
if (!localContent || localContent.trim() === '') {
|
|
1051
|
-
// No local
|
|
1051
|
+
// No local, just write remote
|
|
1052
1052
|
fs.writeFileSync(logFile, remoteContent);
|
|
1053
1053
|
console.log(` Journal ${date} pulled`);
|
|
1054
1054
|
synced++;
|
|
1055
1055
|
} else {
|
|
1056
|
-
// Both exist and differ
|
|
1056
|
+
// Both exist and differ, merge
|
|
1057
1057
|
try {
|
|
1058
1058
|
const localSections = parseJournalSections(localContent);
|
|
1059
1059
|
const remoteSections = parseJournalSections(remoteContent);
|
|
@@ -1065,7 +1065,7 @@ async function pullGeneralJournal(token, agentId) {
|
|
|
1065
1065
|
console.log(` Journal ${date} merged`);
|
|
1066
1066
|
synced++;
|
|
1067
1067
|
} else {
|
|
1068
|
-
// Conflicts
|
|
1068
|
+
// Conflicts, keep local, warn
|
|
1069
1069
|
console.log(` Journal ${date} has conflicts (kept local, run "atris log sync" to resolve)`);
|
|
1070
1070
|
}
|
|
1071
1071
|
} catch {
|
package/commands/pulse.js
CHANGED
|
@@ -748,7 +748,7 @@ function pulseCommand(argv = []) {
|
|
|
748
748
|
const sub = argv[0];
|
|
749
749
|
const rest = argv.slice(1);
|
|
750
750
|
if (sub === '--help' || sub === '-h' || sub === 'help') return showHelp();
|
|
751
|
-
// `pulse <sub> --help` is a help request, not a run
|
|
751
|
+
// `pulse <sub> --help` is a help request, not a run, never execute the
|
|
752
752
|
// subcommand (tick/status/etc. have real side effects) just to show usage.
|
|
753
753
|
if (rest.includes('--help') || rest.includes('-h')) return showHelp();
|
|
754
754
|
switch (sub) {
|